$deploymentScriptVersion = "1.0.0.2" ## Auto-incremented by the build.
##
## Script Name:
##     deploy.ps1
##
## Description:
##     The main script that reads the list of hosts and creates an msdeploy call for each host. Deploys Windows services, Websites, and console 
##     applications.
##
## Pre-Requisites:
##  Target hosts requirements:
##
##       - MSDeploy must be installed on the target hosts (V1, V2/2.1, or V3). The following shows example installation syntax for the case when you are not using the temp agent
##         but rather pre-installing the agent on the target host machines (for V3):
##         *** 64-bit boxes: ***
##             msiexec /I WebDeploy_amd64_en-US.msi /passive ADDLOCAL=ALL LISTENURL=http://+:1443/WebDeployV3/
##         *** 32-bit boxes: ***
##             msiexec /I WebDeploy_x86_en-US.msi /passive ADDLOCAL=ALL LISTENURL=http://+:1443/WebDeployV3/
##
##         The Web Deployment Tool's website is at the following location: http://www.iis.net/download/WebDeploy
##
##       - PowerShell V2 must be installed on the target hosts. PowerShell 2.0 is pre-installed on Windows 7 and Windows Server 2008 R2. For other OS's, it
##         can be installed from the following location: http://support.microsoft.com/kb/968929
##
##	To call this script:
##
##     Open a command prompt and change directory to the directory containing the build artifacts.
##
##        a) For normal deployments, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-Deploy
##
##        b) For rollback deployments, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-Rollback
##
##        c) For test deployments, execute the following (only lists the servers but does not do the deployment):
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-TestMode
##
##        d) For taking a website offline or to stop a Windows service, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-TakeOffline
##
##        e) For bringing a website back online or to start a Windows service, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-BringOnline
##
##        f) For backing up all applications, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-Backup
##
##        g) For checking the status of Windows services, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckServices
##
##        h) For checking the status of IIS websites, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckWebsites
##
##        i) For validating a deployment, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-ValidateDeployment
##
##        j) For a deployment that skips the backup step, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-DeployNoBackup
##
##        k) For restarting a website or a Windows service, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-RestartApp
##
##        l) For checking the configuration of Windows services, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckServiceInfo
##
##        m) For restarting the Web Deployment Agent Service before starting a deployment, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-RestartAgents
##
##        n) For checking the status of the Web Deployment Agent Service, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckAgentStatus
##
##        o) For checking the configuration of the Web Deployment Agent Service, execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckAgentInfo
##
##        p) For checking the deployed code version (from __version.txt), execute the following:
##
##               powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -File deploy.ps1 Run-CheckVersion
##

##################################################################################################
##################################################################################################
## IMPORTANT: Change this value to reflect your msdeploy registration but leave the placeholder.
##            Make sure the port you choose is not blocked by the firewall.

$computerNameTemplate = "http://{0}:1443/WebDeployV3"

##################################################################################################
##################################################################################################

$thisScript = $MyInvocation.MyCommand.Definition 
$scriptPath = Split-Path -parent $thisScript
$scriptName = Split-Path -leaf $thisScript 
$msdeployPath = "C:\Program Files\IIS\Microsoft Web Deploy"
$msdeployV2Path = "C:\Program Files\IIS\Microsoft Web Deploy V2"
$msdeployV3Path = "C:\Program Files\IIS\Microsoft Web Deploy V3"
$toolsMasterPath = "C:\DeployTools"
$skipCredentialsFile = $true  ## Set to false to use a credentials file. If true, $defaultServiceAccountName will be used for service identity.
$defaultServiceAccountName = ""  ## e.g.: NT AUTHORITY\Network Service or NT AUTHORITY\Local Service
$defaultServiceStartOption = "auto"
$defaultServiceFailureResetPeriod = "60000"
$defaultServiceFailureActions = "restart/60000/restart/6000/restart/60000"
$defaultInstallUtilPath = "C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe"
$credentialsFileName = "credentials.xml"
$targethostsPath = Join-Path $scriptPath "targethosts.xml"
$credentialsPath = Join-Path $scriptPath $credentialsFileName
$remoteTempToolsPath = "C:\TempTools"
$powershellCommandPrefix = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File $remoteTempToolsPath\$scriptName "
$additionalMsDeployParameters = " -skip:skipAction=Delete,objectName=dirPath,absolutePath=\\_vti_.*$ -usechecksum -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -disableRule:skipUNC"
$defaultAdditionalMsDeployParametersForWebsites = ""
$additionalMsDeployParametersForWebsiteValidation = " -skip:objectName=setAcl -skip:skipAction=Delete,objectName=dirPath,absolutePath=\\_vti_.*$ -usechecksum"
$streamsFileName = "streams.exe"  ## Use this to delete file-blocking streams from binaries.
$appOfflineDocFileName = "App_Offline.htm"
$appOfflineDocFileNameRegexPattern = "\\App_Offline\.htm"
$currentFolderInfo = New-Object System.IO.DirectoryInfo($scriptPath)
$buildNumber = $currentFolderInfo.Name
$timestamp = Get-Date -format "yyyyMMdd_HHmmss"
$spaceReplacementValue = "+"
$useDebugLogging = $false
$debugLoggingSetting = " -debug "
$verboseLoggingSetting = " " ## " -verbose "
$hostLogFileNamePrefix = "host_"
$hostDeployInaccessibleExt = ".inaccessible"
$hostDeployInProgressExt = ".inprogress"
$hostDeploySucceededExt = ".succeeded"
$hostDeployFailedExt = ".failed"
$hostDeployErrorsExt = ".err"
$hostDeployLogExt = ".log"
$hostPreDeployLogExt = ".pre.log"
$logsFolderNormalMode = "Logs"
$logsFolderStatsFileName = "stats.log"
$cleanupTempToolsFolderOnExit = $false
$onlyRecycleAppPools = $false ## When set to true, overrides the stopIIS setting in targethosts.xml.
$appPoolWaitTimeoutMilliseconds = 5000 ## The maximum time to wait before returning when starting or stopping an app pool in IIS 7.x and above.
$defaultEnvironmentName = "DEV"
$defaultServiceWaitInMilliseconds = 1000 ## 1 second -- this is the pause between a stop and a start of a service.
$defaultProcessWaitBeforeKillInMilliseconds = 1000 ## 1 second. Kill the process if still active after stopping and unintalling.
$defaultNumberOfProcessWaitBeforeKillIterations = 20 ## Number of times to perform the wait for the amount of time specified in "defaultProcessWaitBeforeKillInMilliseconds".
$defaultNumberOfProcessWaitBeforeStartIterations = 15 ## Number of times to perform the wait for the amount of time specified in "defaultServiceWaitInMilliseconds".
$collectAppPoolInformation = $true
$runAsParallelJobs = $true ## Set to true to have each host processed as a separate job. A separate PowerShell process will be spawned on the local box for each host.
$passwordHideString = "XXXXXXXXXX" ## Used to hide the passwords in the logs.
$alwaysValidatePostDeployment = $true ## Set to true so that validation is done after a deployment completes.
$numberOfServiceStartRetriesPostDeployment = 3 ## Number of times to attempt to start a Windows service after deployment.
$alwaysShowValidationResults = $false ## Set to true to have the validation details displayed (verbose output).
$createBackupPreDeployment = $true ## Set to true to create the deployment before calling the msdeploy command.
## TODO: The following variables are not ready for use yet until the corresponding logic is ready in function "Run-Deploy":
##$useBatchingForDeployment = $true ## Set to true to send the deployment requests in batches each of size $batchSize. This is useful only when parallelization is ON and the machine does not have much memory and there are too many target hosts.
##$batchSize = 20 ## This is the number of simultaneous processes that are spun off when parallelization is turned ON and $useBatchingForDeployment is set to 'true'.

##
## ASP.NET Temporary Files cache
##
## Examples: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files
##
$alwaysClearASPNetCache = $false ## Set to true to have the ASP.NET cache deleted when deploying a new website version. The cache version is determined from the $aspNetCacheVersion variable.
$defaultAspNetVersion = "v2.0.50727" ## Possible values for the version are: v2.0.50727 or v4.0.30319.
$aspNetCacheLocationTemplate = "{0}\Microsoft.NET\Framework\{1}\Temporary ASP.NET Files"     ## Cache for 32-bit processes.
$aspNetCacheLocationTemplate64 = "{0}\Microsoft.NET\Framework64\{1}\Temporary ASP.NET Files" ## Cache for 64-bit processes.

##
## Validation results patterns
##
$successValidationPattern1 = "Total changes: 0 (0 added, 0 deleted, 0 updated, 0 parameters changed, 0 bytes copied)"
$successValidationPattern2 = "Info: Total changes: 0 (0 added, 0 deleted, 0 updated, 0 parameters changed, 0 bytes copied)"

## "Logs" folder
$logFolderPath = Join-Path $scriptPath $logsFolderNormalMode
$logFolderStatsFilePath = Join-Path $logFolderPath $logsFolderStatsFileName

##
## Wait intervals in milliseconds.
##
$preSyncCommandWaitInterval = 60000
$postSyncCommandWaitInterval = 60000

##
## Change into the deployment root directory.
##
Set-Location $scriptPath 

$global:MY_SCRIPTS = @()
function Add-Man
{
    param([string]$name,[string]$description,[string]$commandType="Function",[string]$examples="none")
    $myobj = "" | select Name,Description,CommandType,Examples
    $myobj.Name = $name
    $myobj.Description = $description
    $myobj.CommandType = $commandType
    $myobj.Examples = $examples
    $global:MY_SCRIPTS += $myobj
}

function Prepare-Logs-Folder
{
    param([string]$commandName)
    
    ## Back up the Logs folder.
    $logFolderBackupPath = "{0}-{1}" -f $logFolderPath, $timestamp
     
    if([System.IO.Directory]::Exists($logFolderPath))
    {
        copy-item $logFolderPath -destination $logFolderBackupPath -recurse -force
        
        ## Clear contents of the Logs folder.
        remove-item ($logFolderPath + "\*") -recurse -force
    }
    else
    {
        ## Create Logs folder for the first time.
        new-item -itemtype directory -path $logFolderPath
    }
    new-item -itemtype file -path $logFolderStatsFilePath

    $logMessage = ("Command: {0}" -f $commandName)
    $logMessage | Set-Content $logFolderStatsFilePath

    $logMessage = ("Start Time: {0}" -f (get-date))
    $logMessage | Add-Content $logFolderStatsFilePath
}



###########################################################################################################################
###########################################################################################################################
############################################                                      #########################################
############################################           MAIN ENTRY POINT           #########################################
############################################                                      #########################################
###########################################################################################################################
###########################################################################################################################



Add-Man "Run-Deploy" "Acts as the entry point function for the deployment script." "Deployment"
function Run-Deploy()
{
    param([string]$commandName="Deploy", [bool]$rollback, [bool]$testMode, [bool]$bringOnline, [bool]$takeOffline, [bool]$backupOnly, [bool]$checkServices, [bool]$checkWebsites, [bool]$validateDeployment, [bool]$skipPreDeployBackup, [bool]$restartApp, [bool]$checkServiceInfo, [bool]$restartAgents, [bool]$checkAgentStatus, [bool]$checkAgentInfo, [bool]$checkVersion)
    
    Write-Host "Inside deploy.ps1::Run-Deploy... [rollback=$rollback, testMode=$testMode, bringOnline=$bringOnline, takeOffline=$takeOffline, backupOnly=$backupOnly, checkServices=$checkServices, checkWebsites=$checkWebsites, validateDeployment=$validateDeployment, skipPreDeployBackup=$skipPreDeployBackup, restartApp=$restartApp, checkServiceInfo=$checkServiceInfo, restartAgents=$restartAgents, checkAgentStatus=$checkAgentStatus, checkAgentInfo=$checkAgentInfo, checkVersion=$checkVersion]" -ForegroundColor DarkGray
    
    ##
    ## Read the targethosts.xml file and enumerate the hosts.
    ## If a site name is specified at the root node, the site name is queried to get the list of hosts, and the
    ## first node in the hosts collection of nodes is used to get the host metadata.
    ## Otherwise, the list of hosts listed is used 
    ##
    <# The following is an example of a targethosts.xml file's contents:
    
        <?xml version="1.0" encoding="utf-8" ?>
        <hosts environment="DEV" siteName="MySiteName" machinePattern="^ELS01.*WEB.*$" aspNetVersion="v2.0.50727">
          <host name="host1" type="WinService" serviceName="MyTestWindowsService" exeName="MyTestWindowsService.exe" enabled="true" destDir="D:\Services\MyTestWindowsService" authType="NTLM" userName="" password="" useTempAgent="false" backupDir="D:\Data\Backups" uninstallService="false" stopIIS="false" unblockBinaries="false" includeAcls="false" deploySetParametersFile="false" doNotDeleteFilesOnDestination="false" useAppOfflineDoc="false" iisApplicationName="" recycleApp="false" excludeConfigsFromCopy="true" oldExeFullPath="" oldServiceName="" removeAppOfflineDocPostSync="true" additionalFilesToCopy="MyTestWindowsService.exe.RELEASE.config" additionalFilesToCopyTo="MyTestWindowsService.exe.config" clearASPNetCache="false" preDeployCommand="" postDeployCommand="" />
        </hosts>
    #>
    
    if($testMode -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
    {
        Prepare-Logs-Folder $commandName
    }
    
    $customTargetHostsPath = GetArgumentValue("CustomTargetHostsPath")
    if($customTargetHostsPath-eq $null -Or $customTargetHostsPath -eq "")
    {
        $customTargetHostsPath = $targethostsPath
    }
    
    $targetHostsFileExists = [System.IO.File]::Exists($customTargetHostsPath)
    if($targetHostsFileExists -eq $false)
    {
        Write-Host "ERROR:[Run-Deploy]: The specified target hosts file [$customTargetHostsPath] was not found." -BackgroundColor Red -ForegroundColor White
        return
    }

    [xml]$targethosts = Get-Content $customTargetHostsPath
    
    $targethostsMetadata = $targethosts.hosts.host

    $hasSiteName = $false
    $hasMachinePattern = $false
    $sitemetadata = @{}
    $environment = $targethosts.hosts.GetAttribute("environment")
    $siteName = $targethosts.hosts.GetAttribute("siteName")
    $machinePattern = $targethosts.hosts.GetAttribute("machinePattern")
    $aspNetVersion = $targethosts.hosts.GetAttribute("aspNetVersion")

    if(($siteName -ne $null -And $siteName -ne "") -Or ($machinePattern -ne $null -And $machinePattern -ne ""))
    {
        $hasSiteName = ($siteName -ne $null -And $siteName -ne "")
        $hasMachinePattern = ($machinePattern -ne $null -And $machinePattern -ne "")
        
        Write-Host "Site Name: '$siteName'" -ForegroundColor Green
        Write-Host "Machine Pattern: '$machinePattern'" -ForegroundColor Green
        if($aspNetCacheVersion -ne $null -And $aspNetCacheVersion -ne "")
        {
            Write-Host "ASP.NET Version: '$aspNetVersion'" -ForegroundColor Green
        }

        ## Get the attribute values from the first record
        foreach($targethost in $targethostsMetadata)
        {
            $sitemetadata = @{
                environment = $environment
                aspNetVersion = $aspNetVersion
                clearASPNetCache = $targethost.GetAttribute("clearASPNetCache")
                type = $targethost.GetAttribute("type")
                serviceName = $targethost.GetAttribute("serviceName")
                exeName = $targethost.GetAttribute("exeName")
                enabled = $targethost.GetAttribute("enabled")
                destDir = $targethost.GetAttribute("destDir")
                authType = $targethost.GetAttribute("authType")
                userName = $targethost.GetAttribute("userName")
                password = $targethost.GetAttribute("password")
                useTempAgent = $targethost.GetAttribute("useTempAgent")
                backupDir = $targethost.GetAttribute("backupDir")
                uninstallService = $targethost.GetAttribute("uninstallService")
                stopIIS = $targethost.GetAttribute("stopIIS")
                unblockBinaries = $targethost.GetAttribute("unblockBinaries")
                includeAcls = $targethost.GetAttribute("includeAcls")
                deploySetParametersFile = $targethost.GetAttribute("deploySetParametersFile")
                doNotDeleteFilesOnDestination = $targethost.GetAttribute("doNotDeleteFilesOnDestination")
                useAppOfflineDoc = $targethost.GetAttribute("useAppOfflineDoc")
                removeAppOfflineDocPostSync = $targethost.GetAttribute("removeAppOfflineDocPostSync")
                iisApplicationName = $targethost.GetAttribute("iisApplicationName")
                recycleApp = $targethost.GetAttribute("recycleApp")
                excludeConfigsFromCopy = $targethost.GetAttribute("excludeConfigsFromCopy")
                oldExeFullPath = $targethost.GetAttribute("oldExeFullPath")
                oldServiceName = $targethost.GetAttribute("oldServiceName")
                additionalFilesToCopy = $targethost.GetAttribute("additionalFilesToCopy")
                additionalFilesToCopyTo = $targethost.GetAttribute("additionalFilesToCopyTo")
                preDeployCommand = $targethost.GetAttribute("preDeployCommand")
                postDeployCommand = $targethost.GetAttribute("postDeployCommand")
                skipServiceStartPostDeployment = $targethost.GetAttribute("skipServiceStartPostDeployment")
                serviceAccountName = $targethost.GetAttribute("serviceAccountName")
                serviceStartOption = $targethost.GetAttribute("serviceStartOption")
                serviceFailureResetPeriod = $targethost.GetAttribute("serviceFailureResetPeriod")
                serviceFailureActions = $targethost.GetAttribute("serviceFailureActions")
                iisApplicationPoolName = $targethost.GetAttribute("iisApplicationPoolName")
            }

            ## Break since we only need the first element.
            break
        }
    }
    else
    {
        Write-Host "No site name or machine pattern specified. Hosts listed in config file will be used." -ForegroundColor Yellow
    }

    if($hasSiteName -eq $true)
    {
        $servers = Get-Servers-In-Site $siteName
        Write-Host "Fetched servers for site."

        foreach($targethost in $servers)
        {
            $metadata = $sitemetadata
            $metadata.name = $targethost.DNSHostName ## --or-- $targethost.Name
            $metadata.enabled = "true"

            $targethostName = $metadata.name
            $message = "Deploying to target host: {0}" -f $targethostName
            Write-Host $message -BackgroundColor White -ForegroundColor Blue
            
            if ($testMode -ne $true)
            {
                if($runAsParallelJobs -eq $true)
                {
                    Start-Job -name "deploy_$targethostName" -ScriptBlock {
                        param($scriptname, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion)
                        Import-Module -name $scriptname
                        Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                    } -ArgumentList $thisScript, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion
                }
                else
                {
                    Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                }
            }
        }
    }
    else
    {
        if ($hasMachinePattern -eq $true)
        {
            $servers = Get-Servers-By-Pattern $machinePattern
            $serversMessage = "Fetched servers for machine pattern: {0} servers." -f $servers.length
            Write-Host $serversMessage

            foreach($targethost in $servers)
            {
                $metadata = @{
                    environment = $environment
                    aspNetVersion = $aspNetVersion
                    clearASPNetCache = $sitemetadata.clearASPNetCache
                    name = $targethost
                    type = $sitemetadata.type
                    serviceName = $sitemetadata.serviceName
                    exeName = $sitemetadata.exeName
                    enabled = $sitemetadata.enabled
                    destDir = $sitemetadata.destDir
                    authType = $sitemetadata.authType
                    userName = $sitemetadata.userName
                    password = $sitemetadata.password
                    useTempAgent = $sitemetadata.useTempAgent
                    backupDir = $sitemetadata.backupDir
                    uninstallService = $sitemetadata.uninstallService
                    stopIIS = $sitemetadata.stopIIS
                    unblockBinaries = $sitemetadata.unblockBinaries
                    includeAcls = $sitemetadata.includeAcls
                    deploySetParametersFile = $sitemetadata.deploySetParametersFile
                    doNotDeleteFilesOnDestination = $sitemetadata.doNotDeleteFilesOnDestination
                    useAppOfflineDoc = $sitemetadata.useAppOfflineDoc
                    removeAppOfflineDocPostSync = $sitemetadata.removeAppOfflineDocPostSync
                    iisApplicationName = $sitemetadata.iisApplicationName
                    recycleApp = $sitemetadata.recycleApp
                    excludeConfigsFromCopy = $sitemetadata.excludeConfigsFromCopy
                    oldExeFullPath = $sitemetadata.oldExeFullPath
                    oldServiceName = $sitemetadata.oldServiceName
                    additionalFilesToCopy = $sitemetadata.additionalFilesToCopy
                    additionalFilesToCopyTo = $sitemetadata.additionalFilesToCopyTo
                    preDeployCommand = $sitemetadata.preDeployCommand
                    postDeployCommand = $sitemetadata.postDeployCommand
                    skipServiceStartPostDeployment = $sitemetadata.skipServiceStartPostDeployment
                    serviceAccountName = $sitemetadata.serviceAccountName
                    serviceStartOption = $sitemetadata.serviceStartOption
                    serviceFailureResetPeriod = $sitemetadata.serviceFailureResetPeriod
                    serviceFailureActions = $sitemetadata.serviceFailureActions
                    iisApplicationPoolName = $sitemetadata.iisApplicationPoolName
                }
                $metadata.name = $targethost
                $metadata.enabled = "true"

                $message = "Deploying to target host: {0}" -f $targethost
                Write-Host $message -BackgroundColor White -ForegroundColor Blue
                
                if ($testMode -ne $true)
                {
                    if($runAsParallelJobs -eq $true)
                    {
                        Start-Job -name "deploy_$targethost" -ScriptBlock {
                            param($scriptname, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion)
                            Import-Module -name $scriptname
                            Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                        } -ArgumentList $thisScript, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion
                    }
                    else
                    {
                        Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                    }
                }
            }
        }
        else
        {
            Write-Host "Fetching servers listed in targethosts.xml..." -ForegroundColor Green
            
            foreach($targethost in $targethostsMetadata)
            {
                $metadata = @{
                    environment = $environment
                    aspNetVersion = $aspNetVersion
                    clearASPNetCache = $targethost.GetAttribute("clearASPNetCache")
                    name = $targethost.GetAttribute("name")
                    type = $targethost.GetAttribute("type")
                    serviceName = $targethost.GetAttribute("serviceName")
                    exeName = $targethost.GetAttribute("exeName")
                    enabled = $targethost.GetAttribute("enabled")
                    destDir = $targethost.GetAttribute("destDir")
                    authType = $targethost.GetAttribute("authType")
                    userName = $targethost.GetAttribute("userName")
                    password = $targethost.GetAttribute("password")
                    useTempAgent = $targethost.GetAttribute("useTempAgent")
                    backupDir = $targethost.GetAttribute("backupDir")
                    uninstallService = $targethost.GetAttribute("uninstallService")
                    stopIIS = $targethost.GetAttribute("stopIIS")
                    unblockBinaries = $targethost.GetAttribute("unblockBinaries")
                    includeAcls = $targethost.GetAttribute("includeAcls")
                    deploySetParametersFile = $targethost.GetAttribute("deploySetParametersFile")
                    doNotDeleteFilesOnDestination = $targethost.GetAttribute("doNotDeleteFilesOnDestination")
                    useAppOfflineDoc = $targethost.GetAttribute("useAppOfflineDoc")
                    removeAppOfflineDocPostSync = $targethost.GetAttribute("removeAppOfflineDocPostSync")
                    iisApplicationName = $targethost.GetAttribute("iisApplicationName")
                    recycleApp = $targethost.GetAttribute("recycleApp")
                    excludeConfigsFromCopy = $targethost.GetAttribute("excludeConfigsFromCopy")
                    oldExeFullPath = $targethost.GetAttribute("oldExeFullPath")
                    oldServiceName = $targethost.GetAttribute("oldServiceName")
                    additionalFilesToCopy = $targethost.GetAttribute("additionalFilesToCopy")
                    additionalFilesToCopyTo = $targethost.GetAttribute("additionalFilesToCopyTo")
                    preDeployCommand = $targethost.GetAttribute("preDeployCommand")
                    postDeployCommand = $targethost.GetAttribute("postDeployCommand")
                    skipServiceStartPostDeployment = $targethost.GetAttribute("skipServiceStartPostDeployment")
                    serviceAccountName = $targethost.GetAttribute("serviceAccountName")
                    serviceStartOption = $targethost.GetAttribute("serviceStartOption")
                    serviceFailureResetPeriod = $targethost.GetAttribute("serviceFailureResetPeriod")
                    serviceFailureActions = $targethost.GetAttribute("serviceFailureActions")
                    iisApplicationPoolName = $targethost.GetAttribute("iisApplicationPoolName")
                }

                $targethostName = $metadata.name
                $enabledMessage = ""
                
                if($metadata.enabled -ne "true")
                {
                    $enabledMessage = " [Disabled]"
                }
                
                $message = "Deploying to target host: {0}{1}" -f $targethostName, $enabledMessage
                Write-Host $message -BackgroundColor White -ForegroundColor Blue                
                
                if ($testMode -ne $true)
                {
                    if($runAsParallelJobs -eq $true)
                    {
                        Start-Job -name "deploy_$targethostName" -ScriptBlock {
                            param($scriptname, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion)
                            Import-Module -name $scriptname
                            Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                        } -ArgumentList $thisScript, $metadata, $rollback, $bringOnline, $takeOffline, $backupOnly, $checkServices, $checkWebsites, $validateDeployment, $skipPreDeployBackup, $restartApp, $checkServiceInfo, $restartAgents, $checkAgentStatus, $checkAgentInfo, $checkVersion
                    }
                    else
                    {
                        Deploy-To-TargetHost $metadata $rollback $bringOnline $takeOffline $backupOnly $checkServices $checkWebsites $validateDeployment $skipPreDeployBackup $restartApp $checkServiceInfo $restartAgents $checkAgentStatus $checkAgentInfo $checkVersion
                    }
                }
            }
        }
    }
    
    ## Wait for jobs to finish and then cleanup.
    if($runAsParallelJobs -eq $true -And $testMode -ne $true)
    {
        Write-Host "Running Jobs:"
        $jobs

        Write-Host "Waiting for all jobs to finish executing..."

        #Wait-Job $jobs | Receive-Job #-errorAction SilentlyContinue
        #Remove-Job -force $jobs #-errorAction SilentlyContinue
        
        Get-Job | Wait-Job | Receive-Job
        Remove-Job -State Completed  
    }
}

Add-Man "Run-CheckServices" "Checks the state of Windows services." "Deployment"
function Run-CheckServices()
{
    Write-Host "Inside deploy.ps1::Run-CheckServices... Calling Run-Deploy with checkServices=true."
    
    ## "Check Services" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=true, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Check Services" $false $false $false $false $false $true $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-CheckServices."
}

Add-Man "Run-CheckWebsites" "Checks the state of IIS websites." "Deployment"
function Run-CheckWebsites()
{
    Write-Host "Inside deploy.ps1::Run-CheckWebsites... Calling Run-Deploy with checkWebsites=true."
    
    ## "Check Websites" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=true, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Check Websites" $false $false $false $false $false $false $true $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-CheckWebsites."
}

Add-Man "Run-ValidateDeployment" "Checks whether a deployment was completed successfully and that there is no missing content." "Deployment"
function Run-ValidateDeployment()
{
    Write-Host "Inside deploy.ps1::Run-ValidateDeployment... Calling Run-Deploy with validateDeployment=true."
    
    ## "Validate Deployment" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=true, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Validate Deployment" $false $false $false $false $false $false $false $true $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-ValidateDeployment."
}

Add-Man "Run-Rollback" "Acts as the entry point function for the deployment rollbacks. Rolls back the last deployment." "Deployment"
function Run-Rollback()
{
    Write-Host "Inside deploy.ps1::Run-Rollback... Calling Run-Deploy with rollback=true."
    
    ## "Rollback" Rollback=true, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Rollback" $true $false $false $false $false $false $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-Rollback."
}

Add-Man "Run-TestMode" "Acts as the entry point function for the deployment test. Simply skips deployment." "Deployment"
function Run-TestMode()
{
    Write-Host "Inside deploy.ps1::Run-TestMode... Calling Run-Deploy with testMode=true."
    
    ## "Test Mode" Rollback=false, TestMode=true, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Test Mode" $false $true $false $false $false $false $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-TestMode."
}

Add-Man "Run-BringOnline" "Brings a web app online by deleting the App_Offline.htm. It can also be used to start a Windows service." "Deployment"
function Run-BringOnline()
{
    Write-Host "Inside deploy.ps1::Run-BringOnline..."
    
    ## Delete App_Offline.htm
    ## "Bring Online" Rollback=false, TestMode=false, BringOnline=true, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Bring Online" $false $false $true $false $false $false $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-BringOnline."
}

Add-Man "Run-TakeOffline" "Takes a web app offline by copying the App_Offline.htm. It can also be used to stop a Windows service." "Deployment"
function Run-TakeOffline()
{
    Write-Host "Inside deploy.ps1::Run-TakeOffline..."
    
    ## Copy App_Offline.htm
    ## "Take Offline" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Take Offline" $false $false $false $true $false $false $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-TakeOffline."
}

Add-Man "Run-RestartApp" "Restarts a web application or a Windows service." "Deployment"
function Run-RestartApp()
{
    Write-Host "Inside deploy.ps1::Run-RestartApp..."
    
    ## "Restart App" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=true, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Restart App" $false $false $false $false $false $false $false $false $false $true $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-RestartApp."
}

Add-Man "Run-Backup" "Takes a backup of the application into the Backups folder on the target host." "Deployment"
function Run-Backup()
{
    Write-Host "Inside deploy.ps1::Run-Backup..."
    
    ## "Backup" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=true, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Backup" $false $false $false $false $true $false $false $false $false $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-Backup."
}

Add-Man "Run-DeployNoBackup" "Same as Run-Deploy except that it skips the pre-deployment backup. Useful when the backup was already performed with Run-Backup." "Deployment"
function Run-DeployNoBackup()
{
    Write-Host "Inside deploy.ps1::Run-DeployNoBackup..."
    
    ## "Deploy without Backup" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=false, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=true, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Deploy without Backup" $false $false $false $false $false $false $false $false $true $false $false $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-DeployNoBackup."
}

Add-Man "Run-CheckServiceInfo" "Checks the configuration of a Windows service." "Deployment"
function Run-CheckServiceInfo()
{
    Write-Host "Inside deploy.ps1::Run-CheckServiceInfo..."
    
    ## "Check Service Info" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=true, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Check Service Info" $false $false $false $false $false $false $false $false $false $false $true $false $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-CheckServiceInfo."
}

Add-Man "Run-RestartAgents" "Restart the Web Deployment Agent Service on the remote hosts." "Deployment"
function Run-RestartAgents()
{
    Write-Host "Inside deploy.ps1::Run-RestartAgents..."
    
    ## "Restart Agents" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=true, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Restart Agents" $false $false $false $false $false $false $false $false $false $false $false $true $false $false $false
    
    Write-Host "Finished deploy.ps1::Run-RestartAgents."
}

Add-Man "Run-CheckAgentStatus" "Check the status of the Web Deployment Agent Service on the remote hosts." "Deployment"
function Run-CheckAgentStatus()
{
    Write-Host "Inside deploy.ps1::Run-CheckAgentStatus..."
    
    ## "Check Agent Status" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=true, CheckAgentInfo=false, CheckVersion=false
    Run-Deploy "Check Agent Status" $false $false $false $false $false $false $false $false $false $false $false $false $true $false $false
    
    Write-Host "Finished deploy.ps1::Run-CheckAgentStatus."
}

Add-Man "Run-CheckAgentInfo" "Restart the Web Deployment Agent Service on the remote hosts." "Deployment"
function Run-CheckAgentInfo()
{
    Write-Host "Inside deploy.ps1::Run-CheckAgentInfo..."
    
    ## "Check Agent Info" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=true, CheckVersion=false
    Run-Deploy "Check Agent Info" $false $false $false $false $false $false $false $false $false $false $false $false $false $true $false
    
    Write-Host "Finished deploy.ps1::Run-CheckAgentInfo."
}

Add-Man "Run-CheckVersion" "Restart the Web Deployment Agent Service on the remote hosts." "Deployment"
function Run-CheckVersion()
{
    Write-Host "Inside deploy.ps1::Run-CheckVersion..."
    
    ## "Check Version" Rollback=false, TestMode=false, BringOnline=false, TakeOffline=true, BackupOnly=false, checkServices=false, checkWebsites=false, validateDeployment=false, skipPreDeployBackup=false, restartApp=false, checkServiceInfo=false, RestartAgents=false, CheckAgentStatus=false, CheckAgentInfo=false, CheckVersion=true
    Run-Deploy "Check Version" $false $false $false $false $false $false $false $false $false $false $false $false $false $false $true
    
    Write-Host "Finished deploy.ps1::Run-CheckVersion."
}

Add-Man "Deploy-To-TargetHost" "Performs a deployment to a target host using a provided metadata record." "Deployment"
function Deploy-To-TargetHost
{
    param([object]$metadata, [bool]$rollback, [bool]$bringOnline, [bool]$takeOffline, [bool]$backupOnly, [bool]$checkServices, [bool]$checkWebsites, [bool]$validateDeployment, [bool]$skipPreDeployBackup, [bool]$restartApp, [bool]$checkServiceInfo, [bool]$restartAgents, [bool]$checkAgentStatus, [bool]$checkAgentInfo, [bool]$checkVersion)

    $onlyDeployCode = ($rollback -ne $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $backupOnly -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $validateDeployment -ne $true -And $restartApp -ne $true -And $checkServiceInfo -ne $true -And $restartAgents -ne $true -And $checkAgentStatus -ne $true -And $checkAgentInfo -ne $true -And $checkVersion -ne $true)
    Write-Host "OnlyDeployCode=$onlyDeployCode"
    
    Write-Host "Deployment Script Version: $deploymentScriptVersion."

    ###########################################################################################################################
    ###########################################################################################################################
    ############################################                                      #########################################
    ############################################           DEPLOYMENT PREP            #########################################
    ############################################                                      #########################################
    ###########################################################################################################################
    ###########################################################################################################################
    
    $enabled = $metadata.enabled
    $environment = $metadata.environment
    $aspNetVersion = $metadata.aspNetVersion
    [system.collections.arraylist]$errorsList = new-object system.collections.arraylist
    [system.collections.arraylist]$commandOutput = new-object system.collections.arraylist

    ## Proceed only if the current host is enabled
    if ($enabled -eq "true")
    {
        [string[]]$preSyncFunctionList = @()
        [string[]]$postSyncFunctionList = @()
        [string[]]$argumentList = @()
        [string[]]$additionalFilesToCopyArray = @()
        [string[]]$additionalFilesToCopyToArray = @()

        ## The Logs folder for tracking deployment progress.        
        $logFolderPath = Join-Path $scriptPath $logsFolderNormalMode
        
        $deploymentStatusInaccessibleFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeployInaccessibleExt
        $deploymentStatusInProgressFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeployInProgressExt
        $deploymentStatusSucceededFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeploySucceededExt
        $deploymentStatusFailedFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeployFailedExt
        $deploymentErrorsFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeployErrorsExt
        $deploymentLogFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostDeployLogExt
        $deploymentPreDeployLogFileName = "{0}{1}{2}" -f $hostLogFileNamePrefix, $metadata.name, $hostPreDeployLogExt
        
        $deploymentStatusInaccessibleFilePath = Join-Path $logFolderPath $deploymentStatusInaccessibleFileName
        $deploymentStatusInProgressFilePath = Join-Path $logFolderPath $deploymentStatusInProgressFileName
        $deploymentStatusSucceededFilePath = Join-Path $logFolderPath $deploymentStatusSucceededFileName
        $deploymentStatusFailedFilePath = Join-Path $logFolderPath $deploymentStatusFailedFileName
        $deploymentErrorsFilePath = Join-Path $logFolderPath $deploymentErrorsFileName
        $deploymentLogFilePath = Join-Path $logFolderPath $deploymentLogFileName
        $deploymentPreDeployLogFilePath = Join-Path $logFolderPath $deploymentPreDeployLogFileName

        ## Check if the host is accessible.
        [bool]$isAccessible = Is-Host-Accessible $metadata.name
        if($isAccessible -eq $false)
        {
            Write-Host "WARNING: Host is inaccessible." -ForegroundColor Yellow
            
            ## Set the current target host to 'Inaccessible':
            new-item -itemtype file -path $deploymentStatusInaccessibleFilePath

            return
        }
        
        $environmentName = $defaultEnvironmentName
        if($environment -ne $null -And $environment -ne "")
        {
            $environmentName = $environment
        }
        
        if($metadata.additionalFilesToCopy -ne "" -And $metadata.additionalFilesToCopyTo -ne "")
        {
            $additionalFilesToCopyArray = $metadata.additionalFilesToCopy -split ','
            $additionalFilesToCopyToArray = $metadata.additionalFilesToCopyTo -split ','
            
            if($additionalFilesToCopyArray.length -ne $additionalFilesToCopyToArray.length)
            {
                Write-Host "ERROR:[Deploy-To-TargetHost]: Config parameters additionalFilesToCopy and additionalFilesToCopyTo are not equal in size. Review targethosts.xml." -BackgroundColor Red -ForegroundColor White
                return
            }        
        }

        ## If this is a new Windows service or an existing one where service uninstall is requested,
        ## then a credentials file needs to be available to set the Windows service credentials.
        ## If the account settings are already provided on the host item, then we might not need to check the Credentials file.
        $serviceAccountPassword = ""

        if($serviceAccountName -ne $null -And $serviceAccountName -ne "")
        {
            $serviceAccountUserName = $serviceAccountName

            $argumentList += "ServiceAccountUserName={0}" -f $serviceAccountUserName
            $argumentList += "ServiceAccountPassword={0}" -f $serviceAccountPassword
        }
        else
        {
            if($skipCredentialsFile -eq $false)
            {

                $credentialsMasterPath = Join-Path $toolsMasterPath $environmentName 
                $credentialsMasterPath = Join-Path $credentialsMasterPath $credentialsFileName
                $credentialsFileExists = ([System.IO.File]::Exists($credentialsPath) -Or [System.IO.File]::Exists($credentialsMasterPath))
                if($metadata.type -eq 'WinService' -And ($credentialsFileExists -eq $false) -And (($metadata.uninstallService -eq "true") -Or ($metadata.oldExeFullPath -ne $null -And $metadata.oldExeFullPath -ne "")) -And $bringOnline -ne $true -And $takeOffline -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true -And $validateDeployment -ne $true -And $restartApp -ne $true)
                {
                    Write-Host "ERROR:[Deploy-To-TargetHost]: A credentials.xml was not found." -BackgroundColor Red -ForegroundColor White
                    return
                }
                else
                {
                    if($metadata.type -eq 'WinService' -And $credentialsFileExists -eq $true)
                    {
                        $credentialsFileLocation = $credentialsMasterPath
                        if ([System.IO.File]::Exists($credentialsPath))
                        {
                            $credentialsFileLocation = $credentialsPath
                        }
                        [xml]$credentials = Get-Content $credentialsFileLocation
                        $serviceAccountUserName = $credentials.credentials.GetAttribute("username")
                        $serviceAccountPassword = $credentials.credentials.GetAttribute("password")
                        if($serviceAccountUserName -eq $null -Or $serviceAccountUserName -eq "" -Or $serviceAccountPassword -eq $null -Or $serviceAccountPassword -eq $null)
                        {
                            Write-Host "ERROR:[Deploy-To-TargetHost]: An empty username or password were specified in credentials.xml." -BackgroundColor Red -ForegroundColor White
                            return
                        }
                        $argumentList += "ServiceAccountUserName={0}" -f $serviceAccountUserName
                        $argumentList += "ServiceAccountPassword={0}" -f $serviceAccountPassword
                    }
                }
            }
        }

        if($serviceStartOption -ne $null -And $serviceStartOption -ne "")
        {
            $argumentList += "ServiceStartOption={0}" -f $serviceStartOption
        }
        if($serviceFailureResetPeriod -ne $null -And $serviceFailureResetPeriod -ne "")
        {
            $argumentList += "ServiceFailureResetPeriod={0}" -f $serviceFailureResetPeriod
        }
        if($serviceFailureActions -ne $null -And $serviceFailureActions -ne "")
        {
            $argumentList += "ServiceFailureActions={0}" -f $serviceFailureActions
        }

        if($checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
        {
            ## Set the current target host to 'In Progress':
            new-item -itemtype file -path $deploymentStatusInProgressFilePath
        }

        ##
        ## MSDeploy Logging level
        ##
        $msDeployLoggingSetting = $verboseLoggingSetting
        if($useDebugLogging -eq $true)
        {
            $msDeployLoggingSetting += $debugLoggingSetting
        }

        ##
        ## Use temp agent?
        ##
        $tempAgent = ""
        $useTempAgent = $metadata.useTempAgent
        if($useTempAgent -eq "true")
        {
            $tempAgent = ",tempagent=true"
        }
        
        ##
        ## IISRESET?
        ##
        $stopIIS = $metadata.stopIIS
        $recycleApp = $metadata.recycleApp
        
        ##
        ## Unblock binaries.
        ##
        $unblockBinaries = $false
        if($metadata.unblockBinaries -eq "true")
        {
            $unblockBinaries = $true
            $postSyncFunctionList += "Unblock-Binary"
        }

        ##
        ## Do not delete files on destination?
        ##
        $doNotDeleteFilesOnDestination = $metadata.doNotDeleteFilesOnDestination
        if($doNotDeleteFilesOnDestination -eq "true")
        {
            $additionalMsDeployParameters += " -enableRule:DoNotDeleteRule"
        }
                
        ##
        ## Get source package name.
        ##
        $packageNameOnly = ""
        $deployDirectory = Join-Path $scriptPath "deploy"
        $packageName = [System.IO.Directory]::GetFiles($deployDirectory, "*.zip",[System.IO.SearchOption]::TopDirectoryOnly)
        
        if($packageName -ne $null)
        {
            $topMostPackageName = $packageName[0]
            
            $packagePath = [System.IO.Path]::Combine([System.IO.Path]::Combine($scriptPath, "deploy"), $topMostPackageName)
            Write-Host "Deploy-To-TargetHost::packagePath: $packagePath."

            [System.IO.FileInfo]$packageFileInfo = new-object System.IO.FileInfo($topMostPackageName)
            $packageNameOnly = $packageFileInfo.Name
            #Write-Host $packageNameOnly
            
            if($validateDeployment -eq $true -Or $alwaysValidatePostDeployment -eq $true)
            {
                $argumentList += "PackageNameOnly={0}" -f $packageNameOnly
            }
        }
        else
        {
            $dirPath = [System.IO.Path]::Combine($scriptPath, "deploy")
            Write-Host "Deploy-To-TargetHost::dirPath: $dirPath."
        }

        ##
        ## Set up any Pre- or Post- Deployment command line calls.
        ##
        $preDeployCommand = $metadata.preDeployCommand
        if($preDeployCommand -ne $null -And $preDeployCommand -ne "")
        {
            $argumentList += "PreDeployCommand={0}" -f ($preDeployCommand.Replace(" ", $spaceReplacementValue))
            $preSyncFunctionList += "RunPreDeployCommand"
        }

        $postDeployCommand = $metadata.postDeployCommand
        if($postDeployCommand -ne $null -And $postDeployCommand -ne "")
        {
            $argumentList += "PostDeployCommand={0}" -f ($postDeployCommand.Replace(" ", $spaceReplacementValue))
            $postSyncFunctionList += "RunPostDeployCommand"
        }
        
        ##
        ## Deploy SetParameters file?
        ##
        if($deploySetParametersFile -eq "true")
        {
            if($packageName -ne $null)
            {
                $additionalMsDeployParameters += " -setParamFile:'$packageName.SetParameters.xml'"
            }
        }
            
        ##
        ## Get destination directory.
        ##
        $destDir = $metadata.destDir

        ##
        ## Get the EXE filename (for Windows services and Console apps).
        ##
        $exeName = $metadata.exeName
        if($exeName -ne $null -And $exeName -ne "")
        {
            $argumentList += "ExeName={0}" -f $exeName
        }

        ##
        ## Get the Old EXE fulle path including filename (for Windows services that are having the name and path changed).
        ##
        $oldExeFullPath = $metadata.oldExeFullPath
        if($oldExeFullPath -ne $null -And $oldExeFullPath -ne "")
        {
            $argumentList += "OldExeFullPath={0}" -f $oldExeFullPath
        }

        ##
        ## Get host type (WinService, Website, or Console).
        ##
        $hostType = $metadata.type
        if($hostType -eq $null -Or $hostType -eq "")
        {
            $hostType = "Console" # if host type is not specified, we assume a console app.
        }
        $argumentList += "HostType={0}" -f $hostType

        ##
        ## Remove App_Offline.htm After Website Sync is Finished?
        ##
        if($metadata.removeAppOfflineDocPostSync -ne "true" -And $hostType -eq 'Website')
        {
            $additionalMsDeployParameters += (" -skip:objectName=filePath,absolutePath=.*$appOfflineDocFileNameRegexPattern$")
        }
        
        ##
        ## Get the service name (for Windows services).
        ##
        $serviceName = $metadata.serviceName
        if($serviceName -ne $null -And $serviceName -ne "")
        {
            $argumentList += "ServiceName={0}" -f ($serviceName.Replace(" ", $spaceReplacementValue))
        }
        $argumentList += "ServicePath={0}" -f ($destDir.Replace(" ", $spaceReplacementValue))

        ##
        ## Get the old service name (for Windows services that are having the name and path changed).
        ##
        $oldServiceName = $metadata.oldServiceName
        if($oldServiceName -ne $null -And $oldServiceName -ne "")
        {
            ## Replace spaces with $spaceReplacementValue ("+") in order to avoid issues with spaces and also to avoid having
            ## to add excessive escaping. OldServiceName is the only place we have this problem.
            $argumentList += "OldServiceName={0}" -f ($oldServiceName.Replace(" ", $spaceReplacementValue))
        }

        ##
        ## Get target attributes.
        ##
        $targetHost = $metadata.name
        if($useTempAgent -ne "true")
        {
            $targetHost = $computerNameTemplate -f $targetHost
        }
        Write-Host "MSDeploy Target Host: '$targetHost'."
        
        $authType = $metadata.authType
        $useAppOfflineDoc = $metadata.useAppOfflineDoc
        $sourceProviderName = "package"
        $destProviderName = "contentPath"
        $sourcePath = $packagePath
        $additionalSourceParams = ""
        $additionalTargetParams = $tempAgent

        ##
        ## Include ACLs?
        ##
        $includeAcls = $metadata.includeAcls
        if($includeAcls -eq "true")
        {
            $additionalSourceParams += ",includeAcls=true"
        }
        else
        {
            $additionalSourceParams += ",includeAcls=false"
        }

        ##
        ## Backup to backupDir if provided.
        ##
        $backupDir = $metadata.backupDir
        $backupDirPrefix = "Pre-"
        if($backupDir -ne $null -And $backupDir -ne "")
        {
            if($skipPreDeployBackup -ne $true -And $createBackupPreDeployment -ne $true)
            {
                $preSyncFunctionList += "Backup-Application"
            }
            $argumentList += "RootBackupDir={0}" -f $backupDir
            if($rollback -eq $true) { $backupDirPrefix = "RolledBack-" }
            $backupDir = Join-Path $backupDir ($backupDirPrefix + $buildNumber + "-" + $timestamp)
            $argumentList += "BackupDir={0}" -f $backupDir
        }
        
        ##
        ## Remove read-only attribute before attempting code sync.
        ## Accounts for read-only files that might have been dropped manually straight from source control (TFS).
        ##
        if($onlyDeployCode -eq $true)
        {
            $preSyncFunctionList += "Remove-ReadOnly"
        }

        ##
        ## Remove hidden attribute before attempting code sync.
        ## Accounts for hidden files like thumbs.db.
        ##
        if($onlyDeployCode -eq $true)
        {
            $preSyncFunctionList += "Remove-Hidden"
        }

        ##
        ## Kill services.msc as it can prevent the uninstallation of Windows services since it locks some registry keys.
        ## Do this only if service uninstallation has been requested.
        ##
        $uninstallService = $metadata.uninstallService
        if($onlyDeployCode -eq $true -And $hostType -eq 'WinService' -And $uninstallService -eq "true")
        {
            $preSyncFunctionList += "Kill-ServicesMsc"
        }
        
        ## If additional files are requested to be copied (outside of the package contents), then instruct remote script to skip starting the service as it will be started after the files are copied.
        if($additionalFilesToCopyArray.length -gt 0 -And $backupOnly -ne $true -And $rollback -ne $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $restartApp -ne $true)
        {
            $argumentList += "SkipServiceStart=true"
        }
        
        ##
        ## Exclude overwriting config files?
        ##
        if($metadata.excludeConfigsFromCopy -eq "true")
        {
            if($additionalFilesToCopyArray.length -gt 0)
            {
                $index = 0
                foreach($additionalFileToCopy in $additionalFilesToCopyArray)
                {
                    $additionalFileToCopyTo = $additionalFilesToCopyToArray[$index]
                    $additionalMsDeploySkipParameter = (" -skip:objectName=filePath,absolutePath={0}$" -f $additionalFileToCopyTo)
                    $additionalMsDeployParameters += $additionalMsDeploySkipParameter
                    $index += 1
                }
            }
            
            ## Skip updating any configs. This can be the case when configs are not provided with the package and the current target configs need to be kept untouched.
            if($additionalFilesToCopyArray.length -le 0)
            {
                $additionalMsDeployParameters += " -skip:objectName=filePath,absolutePath=.*\.config$"
            }
        }
        
        $iisApplicationName = $metadata.iisApplicationName
        $iisApplicationPoolName = $metadata.iisApplicationPoolName

        switch -case ($hostType)
        {
            'WinService'
            {
                $preSyncFunctionList += "Stop-Service"

                ##
                ## Uninstall the existing service first (for Windows Services).
                ##
                if($uninstallService -eq "true")
                {
                    $preSyncFunctionList += "Uninstall-Service"
                    $postSyncFunctionList += "Install-Service"
                }

                ## Ensure that the Windows service was unloaded from memory before deploying binaries.
                $preSyncFunctionList += "Ensure-Process-Unloaded"
                
                if($skipServiceStartPostDeployment -ne "true")
                {
                    $postSyncFunctionList += "Start-Service"
                }
            }
            'Website'
            {
                if($packageName -ne $null)
                {
                    $destProviderName = "auto"
                }
                
                if($aspNetVersion -ne $null -And $aspNetVersion -ne "")
                {
                    $argumentList += "AspNetVersion={0}" -f $aspNetVersion
                }
                
                if ($iisapplicationname -ne $null -and $iisapplicationname -ne "")
                {
                    ## Make sure that the application name specified in targethosts.xml matches what we have defined in IIS (in case it is not overriden in the build definition.
                    $additionalmsdeployparameters += " -setParam:name=ApplicationPath,value='$iisapplicationname'"
                    ## Make sure the application path specified in targethosts.xml matches the destDir specified (also in targethosts.xml). This will override the existing setting in IIS. 
                    $additionalmsdeployparameters += " -setparam:type=destinationvirtualdirectory,scope='$iisapplicationname',value='$destdir'"
                }
                
                ## Add any MSDeploy parameters for websites apps.
                $additionalMsDeployParameters += $defaultAdditionalMsDeployParametersForWebsites
                
                if($collectAppPoolInformation -eq $true)
                {
                    $preSyncFunctionList += "List-AppPools"
                }
                
                if($validateDeployment -eq $true -Or $alwaysValidatePostDeployment -eq $true -Or $onlyRecycleAppPools -eq $true -Or $recycleApp -eq "true")
                {
                    $argumentList += "IisApplicationPoolName={0}" -f ($iisApplicationPoolName.Replace(" ", $spaceReplacementValue))
                }

                $argumentList += "ServiceName={0}" -f ($iisApplicationName.Replace(" ", $spaceReplacementValue))

                if($alwaysClearASPNetCache -eq $true -Or $metadata.clearASPNetCache -eq "true")
                {
                    $postSyncFunctionList += "Clear-Temp-AspNet-Cache"
                }
                
                if(($onlyRecycleAppPools -eq $true) -Or ($recycleApp -eq "true"))
                {
                    $preSyncFunctionList += "Stop-AppPool"
                    if($skipServiceStartPostDeployment -ne "true")
                    {
                        $postSyncFunctionList += "Start-AppPool"
                    }
                }
                elseif($stopIIS -eq "true")
                {
                    $preSyncFunctionList += "Stop-Website"
                    if($skipServiceStartPostDeployment -ne "true")
                    {
                        $postSyncFunctionList += "Start-Website"
                    }
                }
                                
                if($useAppOfflineDoc -eq "true")
                {
                    $preSyncFunctionList += "Take-Website-Offline"
                    if($metadata.removeAppOfflineDocPostSync -eq "true")
                    {
                        $postSyncFunctionList += "Bring-Website-Online"
                    }
                }
            }
            default
            {
                # Console.
                ## Ensure that the console program was unloaded from memory before deploying the new binary.
                $preSyncFunctionList += "Ensure-Process-Unloaded"
            }
        }
            
        ##
        ## Add msdeploy to the Path environment variable.
        ##
        if([System.IO.Directory]::Exists($msdeployV3Path))
        {
            $env:path = $env:path + ";" + $msdeployV3Path
        }
        elseif([System.IO.Directory]::Exists($msdeployV2Path))
        {
            $env:path = $env:path + ";" + $msdeployV2Path
        }
        else
        {
            if([System.IO.Directory]::Exists($msdeployPath))
            {
                $env:path = $env:path + ";" + $msdeployPath
            }
            else
            {
                Write-Host "ERROR:[Deploy-To-TargetHost]: MSDeploy does not seem to be installed on this machine. Could not find '$msdeployPath', '$msdeployV2Path', or '$msdeployV3Path'." -BackgroundColor Red -ForegroundColor White
                return
            }
        }
                    
        $postSyncFunctionList += "Do-PostSync"

        if($rollback -eq $true)
        {
            $preSyncFunctionList += "Rollback-To-LKG"
            $argumentList += "IsRollback=true"
        }
        
        ###########################################################################################################################
        ###########################################################################################################################
        ############################################                                      #########################################
        ############################################          DEPLOYMENT LOGIC            #########################################
        ############################################                                      #########################################
        ###########################################################################################################################
        ###########################################################################################################################
        
        
        ## Flatten the arrays.
        $flattenedPreSyncFunctionList = $preSyncFunctionList -join ' '
        $flattenedPostSyncFunctionList = $postSyncFunctionList -join ' '
        $flattenedArgumentList = $argumentList -join ' '
        $commandExecutionResult = $false ## Stores the value of $?. Call right after a command is executed.
        $lastExitCodeValue = 0 ## Stores the value of $LASTEXITCODE. Call right after a command is executed.

        try
        {
            [bool]$preInstallValidationSucceeded = $true
            
            Write-Host "Deployment Script Version: $deploymentScriptVersion."
            $addIndex = $commandOutput.Add("Deployment Script Version: $deploymentScriptVersion.")

            #######################################################################################################################
            ##                                                                                                                   ##
            ## 1) First sync the PS command file to a destination folder. It will be cleaned up in the postSyncOnSuccess script. ##
            ##                                                                                                                   ##
            #######################################################################################################################
            if($bringOnline -ne $true -And $takeOffline -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true -And ($validateDeployment -ne $true -Or ($validateDeployment -eq $true -And $hostType -eq 'Website')))
            {
                ## First, we need to validate that the Web Deployment Agent Service (MsDepSvc) is running.
                $TargetComputerName = $metadata.name
                $DepServiceName = "MsDepSvc"
                Write-Host "Checking if 'Web Deployment Agent Service ($DepServiceName) is installed and running on target host..."
                $addIndex = $commandOutput.Add("Checking if 'Web Deployment Agent Service ($DepServiceName) is installed and running on target host...")
                
                $servicePrior = Get-Service -computername $TargetComputerName -name "$DepServiceName" -errorAction SilentlyContinue
                if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                {
                    Write-Host $servicePrior.status
                    $addIndex = $commandOutput.Add($servicePrior.status)
                    
                    if($servicePrior.status -eq "Running")
                    {
                        Write-Host "'$DepServiceName' is already in RUNNING state." -foregroundcolor green
                        $addIndex = $commandOutput.Add("'$DepServiceName' is already in RUNNING state.")
                    }
                    else
                    {
                        Write-Host "Attempting to start '$DepServiceName'..." 
                        $addIndex = $commandOutput.Add("Attempting to start '$DepServiceName'...")
                        
                        $commandLine = "sc `"\\{0}`" start `"{1}`"" -f $TargetComputerName, $DepServiceName
                        $addIndex = $commandOutput.Add("COMMAND: $commandLine")
                        Write-Host "COMMAND: $commandLine" 
                            
                        [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)
                        $addIndex = $commandOutput.Add($tempCommandOutput)

                        ## Wait a little bit before checking service status
                        Start-Sleep -m $defaultServiceWaitInMilliseconds

                        $servicePrior = Get-Service -computername $TargetComputerName -name "$DepServiceName" -errorAction SilentlyContinue
                        if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                        {
                            Write-Host $servicePrior.status
                            $addIndex = $commandOutput.Add($servicePrior.status)
                            
                            if($servicePrior.status -eq "Running")
                            {
                                Write-Host "Successfully started service '$DepServiceName'." -foregroundcolor green
                                $addIndex = $commandOutput.Add("Successfully started service '$DepServiceName'.")
                            }
                            else
                            {
                                $preInstallValidationSucceeded = $false
                                
                                $errorMessage = $("ERROR:[MsDepSvc]: Failed to start '{0}'." -f $DepServiceName)
                                $addIndex = $commandOutput.Add($errorMessage)
                                $addIndex = $errorsList.Add($errorMessage)
                                Write-Host $errorMessage -foregroundcolor red
                            }
                        }
                        else
                        {
                            $preInstallValidationSucceeded = $false
                            
                            $errorMessage = $("ERROR:[MsDepSvc]: Failed to get the status of service '{0}' after starting it." -f $DepServiceName)
                            $addIndex = $commandOutput.Add($errorMessage)
                            $addIndex = $errorsList.Add($errorMessage)
                            Write-Host $errorMessage -foregroundcolor red
                        }
                    }
                }
                else
                {
                    $preInstallValidationSucceeded = $false
                    
                    $errorMessage = $("ERROR:[MsDepSvc]: Failed to get the status of service '{0}'. The service might not have been installed correctly or the account running the script might not have sufficient permissions." -f $DepServiceName)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }

                ############################################################################################
                ##                                                                                        ##
                ## 2) Then sync the deployment script to the target host.                                 ##
                ##                                                                                        ##
                ############################################################################################

                if($preInstallValidationSucceeded -eq $true)
                {
                    $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} -usechecksum" -f $thisScript, $targetHost, $authType, (Join-Path $remoteTempToolsPath $scriptName), $tempAgent, $msDeployLoggingSetting )
                    ##Write-Host $commandLine
                    $addIndex = $commandOutput.Add($commandLine)
                    [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)

                    ## Verify that the command executed successfully.
                    $commandExecutionResult = $?
                    $lastExitCodeValue = $LASTEXITCODE

                    $addIndex = $commandOutput.Add($tempCommandOutput)
                    
                    if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                    {
                        $preInstallValidationSucceeded = $false
                        
                        $errorMessage = $("ERROR:[SyncDeployScript]: Failed to sync the deployment script to the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                        $addIndex = $commandOutput.Add($errorMessage)
                        $addIndex = $errorsList.Add($errorMessage)
                        Write-Host $errorMessage -foregroundcolor red
                    }
                    else
                    {
                        $addIndex = $commandOutput.Add("Successfully sync'ed the deployment script to the target host.")
                    }
                    
                    ## TODO: Validate and fail deployment on failed validation.
                }
            }

            ##############################################################################################################################
            ##                                                                                                                          ##
            ## 3) Conditionally copy streams.exe to the destination folder. It will also be cleaned up in the postSyncOnSuccess script. ##
            ##                                                                                                                          ##
            ##############################################################################################################################
            if($preInstallValidationSucceeded -eq $true -And $unblockBinaries -eq $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true -And $validateDeployment -ne $true -And $restartApp -ne $true)
            {
                $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} -usechecksum" -f (Join-Path $scriptPath $streamsFileName), $targetHost, $authType, (Join-Path $remoteTempToolsPath $streamsFileName), $tempAgent, $msDeployLoggingSetting )
                ##Write-Host $commandLine
                $addIndex = $commandOutput.Add($commandLine)
                [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)

                ## Verify that the command executed successfully.
                $commandExecutionResult = $?
                $lastExitCodeValue = $LASTEXITCODE

                $addIndex = $commandOutput.Add($tempCommandOutput)
                
                if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                {
                    $preInstallValidationSucceeded = $false
                    
                    $errorMessage = $("ERROR:[SyncStreamsExe]: Failed to sync streams.exe to the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }
                else
                {
                    $addIndex = $commandOutput.Add("Successfully sync'ed streams.exe to the target host.")
                }
                
                ## TODO: Validate and fail deployment on failed validation.
            }
            
            ##################################################################################################################################
            ##                                                                                                                              ##
            ## 4) Conditionally copy App_Offline.htm to the destination folder. It will also be cleaned up in the postSyncOnSuccess script. ##
            ##                                                                                                                              ##
            ##################################################################################################################################
            if($preInstallValidationSucceeded -eq $true -And $useAppOfflineDoc -eq "true" -And $bringOnline -ne $true -And $takeOffline -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true -And $validateDeployment -ne $true -And $restartApp -ne $true)
            {
                $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} -usechecksum" -f (Join-Path $scriptPath $appOfflineDocFileName), $targetHost, $authType, (Join-Path $remoteTempToolsPath $appOfflineDocFileName), $tempAgent, $msDeployLoggingSetting )
                ##Write-Host $commandLine
                $addIndex = $commandOutput.Add($commandLine)
                [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)

                ## Verify that the command executed successfully.
                $commandExecutionResult = $?
                $lastExitCodeValue = $LASTEXITCODE

                $addIndex = $commandOutput.Add($tempCommandOutput)
                
                if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                {
                    $preInstallValidationSucceeded = $false
                    
                    $errorMessage = $("ERROR:[SyncAppOffline]: Failed to sync App_Offline.htm to the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }
                else
                {
                    $addIndex = $commandOutput.Add("Successfully sync'ed App_Offline.htm to the target host.")
                }

                ## TODO: Validate and fail deployment on failed validation.
            }
            
            ###########################################################################################
            ##                                                                                       ##
            ## 5) Copy the deployment package if deployment verification is requested for a website. ##
            ##                                                                                       ##
            ###########################################################################################
            if($preInstallValidationSucceeded -eq $true -And $hostType -eq 'Website' -And (($validateDeployment -eq $true) -Or ($alwaysValidatePostDeployment -eq $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true -And $validateDeployment -ne $true -And $restartApp -ne $true)))
            {
                $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} " -f $sourcePath, $targetHost, $authType, (Join-Path (Join-Path $remoteTempToolsPath $iisApplicationName) $packageNameOnly), $tempAgent, $msDeployLoggingSetting )
                ##Write-Host $commandLine
                $addIndex = $commandOutput.Add($commandLine)
                [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)
                
                ## Verify that the command executed successfully.
                $commandExecutionResult = $?
                $lastExitCodeValue = $LASTEXITCODE

                $addIndex = $commandOutput.Add($tempCommandOutput)
                
                if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                {
                    $preInstallValidationSucceeded = $false
                    
                    $errorMessage = $("ERROR:[SyncDeployPackage]: Failed to sync the deployment package to temp location on the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }
                else
                {
                    $addIndex = $commandOutput.Add("Successfully sync'ed the deployment package to temp location on the target host.")
                }

                ## TODO: Validate and fail deployment on failed validation.
            }

            [string[]]$validationResult = ""
            [bool]$validationSucceeded = $true ## Will be set to false if validation is requested and fails.
            [bool]$backupValidationSucceeded = $false

            ########################################################################################
            ##                                                                                    ##
            ## 6) Try to create a backup before calling the deployment script:                    ##
            ##                                                                                    ##
            ########################################################################################
            if($preInstallValidationSucceeded -eq $true -And $backupDir -ne $null -And $backupDir -ne "" -And $onlyDeployCode -eq $true -And $createBackupPreDeployment -eq $true -And $skipPreDeployBackup -ne $true)
            {
                $addIndex = $commandOutput.Add("Performing backup...")
                $commandLine = $("msdeploy.exe -verb:sync -source:runCommand=`"{0} Backup-Application {1} < NUL`",waitInterval={6} -dest:auto,computerName=`"{2}`",authType={3}{4} {5} " -f $powershellCommandPrefix, $flattenedArgumentList, $targetHost, $authType, $tempAgent, $msDeployLoggingSetting, $preSyncCommandWaitInterval )
                [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)
                
                ## Verify that the command executed successfully.
                $commandExecutionResult = $?
                $lastExitCodeValue = $LASTEXITCODE

                $addIndex = $commandOutput.Add($tempCommandOutput)
                
                if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                {
                    $errorMessage = $("ERROR:[Backup]: Failed to backup the application folder on the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }

                $addIndex = $commandOutput.Add("--------------")
                
                $addIndex = $commandOutput.Add("Validating backup...")
                $commandLine = $("msdeploy.exe -verb:sync -source:dirPath=`"\\{13}\{0}`"{11} -dest:dirPath=`"\\{13}\{3}`" {12} {10} -skip:objectName=dirPath,absolutePath=\\_vti_.*$ -skip:skipAction=Delete,objectName=dirPath,absolutePath=\\_vti_.*$ -whatif" -f $destDir.replace(":", "$"), $targetHost, $authType, $backupDir.replace(":", "$"), $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting, $metadata.name )
                
                $addIndex = $commandOutput.Add($commandLine)
                
                ## Execute the validation and get the output.
                [string[]]$validationResult = (cmd.exe /C $commandLine)
                
                ## Verify that the command executed successfully.
                $commandExecutionResult = $?
                $lastExitCodeValue = $LASTEXITCODE

                if($alwaysShowValidationResults -eq $true)
                {
                    Write-Host $validationResult -ForegroundColor Gray
                }
                
                $addIndex = $commandOutput.Add($validationResult)

                if($commandExecutionResult -eq $false -Or $lastExitCodeValue -ne 0)
                {
                    $errorMessage = $("ERROR:[BackupVerify]: Failed to execute backup verification of the application folder on the target host. Result: {0}. ExitCode: {1}." -f $commandExecutionResult, $lastExitCodeValue)
                    $addIndex = $commandOutput.Add($errorMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                    Write-Host $errorMessage -foregroundcolor red
                }
                
                if(($validationResult -contains $successValidationPattern1) -eq $true -Or ($validationResult -contains $successValidationPattern2) -eq $true)
                {
                    $backupValidationSucceeded = $true
                    
                    $logMessage = $("Backup Validation [{0}]: SUCCESS" -f $metadata.name)
                    Write-Host $logMessage -ForegroundColor Green
                }
                else
                {
                    $validationSucceeded = $false
                    
                    $logMessage = $("Backup Validation [{0}]: FAILED" -f $metadata.name)
                    Write-Host $logMessage -ForegroundColor Red
                    
                    $errorMessage = $("ERROR:[Deploy-To-TargetHost]: {0}" -f $logMessage)
                    $addIndex = $errorsList.Add($errorMessage)
                }
            }
            else
            {
                ## There is no backup folder specified in the metadata.
                $backupValidationSucceeded = $true
            }

            ########################################################################################
            ##                                                                                    ##
            ##                             Write pre-requisites log file.                         ##
            ##                                                                                    ##
            ########################################################################################

            ## At this point we can flush the pre-deployment log and clear it for deployment logging.
            if($commandOutput.Count -gt 0)
            {
                Write-To-Log $deploymentPreDeployLogFilePath $commandOutput $errorsList ""
                $commandOutput.Clear()
                $commandOutput = new-object system.collections.arraylist
            }

            ########################################################################################
            ##                                                                                    ##
            ##       End of Pre-Requisites Prep Work. Time to do the actual deployment work:      ##
            ##                                                                                    ##
            ########################################################################################

            if($preInstallValidationSucceeded -eq $true -And $backupValidationSucceeded -eq $true)
            {
                $deployAdditionalFiles = $false
                ##
                ## Call msdeploy.
                ##
                if($rollback -eq $true)
                {
                    $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {10} -preSync:runCommand=`"{5} {6} {8} < NUL`",waitInterval={9} -postSync:runCommand=`"{5} {7} {8} < NUL`",waitInterval={9} -postSyncOnSuccess:runCommand=`"{5} Do-Finish < NUL`",waitInterval={9} -usechecksum" -f $thisScript, $targetHost, $authType, (Join-Path $remoteTempToolsPath $scriptName), $tempAgent, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $msDeployLoggingSetting )
                }
                else
                {
                    if ($bringOnline -eq $true -Or $takeOffline -eq $true -Or $restartApp -eq $true)
                    {
                        if ($hostType -eq 'Website')
                        {
                            if ($bringOnline -eq $true)
                            {
                                $commandLine = $("msdeploy.exe -verb:delete -dest:filePath=`"{0}`",computerName=`"{1}`",authType={2}{3} {4} " -f (Join-Path $destDir $appOfflineDocFileName), $targetHost, $authType, $tempAgent, $msDeployLoggingSetting )
                            }
                            elseif ($takeOffline -eq $true)
                            {
                                $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} " -f (Join-Path $scriptPath $appOfflineDocFileName), $targetHost, $authType, (Join-Path $destDir $appOfflineDocFileName), $tempAgent, $msDeployLoggingSetting )
                            }
                            else #restartApp=true
                            {
                                ## TODO
                                Write-Host "Inside deploy.ps1::Run-Deploy: Restarting IIS app '$iisApplicationName' on machine '$targetHost'... (NOT IMPLEMENTED YET)" -ForegroundColor Yellow
                                $commandLine = ""
                            }
                        }
                        else
                        {
                            if ($hostType -eq 'WinService')
                            {
                                if ($bringOnline -eq $true)
                                {
                                    Write-Host "Inside deploy.ps1::Run-Deploy: Starting service '$serviceName' on machine '$targetHost'..."
                                    $commandLine = $("SC `"\\{0}`" START `"{1}`"" -f $metadata.name, $serviceName)
                                }
                                elseif ($takeOffline -eq $true)
                                {
                                    Write-Host "Inside deploy.ps1::Run-Deploy: Stopping service '$serviceName' on machine '$targetHost'..."
                                    $commandLine = $("SC `"\\{0}`" STOP `"{1}`"" -f $metadata.name, $serviceName)
                                }
                                else #restartApp=true
                                {
                                    Remote-Restart-Service $metadata.name $targetHost $serviceName $exeName $commandOutput
                                    $commandLine = "" ## To skip further execution of commands.
                                }
                            }
                        }
                    }
                    elseif ($checkServices -eq $true -Or $checkWebsites -eq $true -Or $checkServiceInfo -eq $true)
                    {
                        if($checkServices -eq $true)
                        {
                            Write-Host "Inside deploy.ps1::Run-Deploy: Checking state of service '$serviceName' on machine '$targetHost'..."
                            $commandLine = $("SC `"\\{0}`" QUERY `"{1}`"" -f $metadata.name, $serviceName)
                        }
                        elseif($checkWebsites -eq $true)
                        {
                            Write-Host "Inside deploy.ps1::Run-Deploy: Checking state of IIS app '$iisApplicationName' on machine '$targetHost'... (NOT IMPLEMENTED YET)" -ForegroundColor Yellow
                            ## TODO: Check the status of the app pool.
                            $commandLine = ""
                        }
                        else ## checkServiceInfo
                        {
                            Write-Host "Inside deploy.ps1::Run-Deploy: Checking configuration of service '$serviceName' on machine '$targetHost'..."
                            $commandLine = $("SC `"\\{0}`" qc `"{1}`"" -f $metadata.name, $serviceName)
                        }
                    }
                    else
                    {
                        if ($backupOnly -ne $true)
                        {
                            if($validateDeployment -eq $true)
                            {
                                if($packageName -ne $null)
                                {
                                    if ($hostType -eq 'Website')
                                    {
                                        ## There is a bug in MSDeploy V1/V2 with -whatif when performed remotely on website packages (not the case for normal archives). It will return incorrect results
                                        ## and seems to indicate that the sync failed whereas it did not in reality.
                                        ## The workaround here is to first copy the package to the destination and then do the comparison there.
                                        ## TODO: Conditionally check if MSDeploy V3 is installed on the deployment box, and if so, execute the command that follows, otherwise fall back to the workaround.
                                        ##$commandLine = $("msdeploy.exe -verb:sync -source:{10}=`"{0}`"{13} -dest:{11},computerName=`"{1}`",authType={2}{4} {14} {12} -whatif" -f $sourcePath, $targetHost, $authType, $destDir, $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $sourceProviderName, $destProviderName, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting )
                                        
                                        ## The workaround...
                                        $commandLine = $("msdeploy.exe -verb:sync -source:runCommand=`"{0} Validate-Web-Application {1} < NUL`",waitInterval={6} -dest:auto,computerName=`"{2}`",authType={3}{4} {5} " -f $powershellCommandPrefix, $flattenedArgumentList, $targetHost, $authType, $tempAgent, $msDeployLoggingSetting, $preSyncCommandWaitInterval )
                                    }
                                    else
                                    {
                                        $commandLine = $("msdeploy.exe -verb:sync -source:{10}=`"{0}`"{13} -dest:{11}=`"\\{15}\{3}`" {14} {12} -skip:skipAction=Delete,objectName=filePath,absolutePath=.*\.InstallLog$ -skip:skipAction=Delete,objectName=filePath,absolutePath=.*\.InstallState$ -whatif" -f $sourcePath, $targetHost, $authType, $destDir.replace(":", "$"), $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $sourceProviderName, $destProviderName, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting, $metadata.name )
                                    }
                                }
                                else
                                {
                                    $commandLine = $("msdeploy.exe -verb:sync -source:dirPath=`"{0}`"{11} -dest:dirPath=`"\\{13}\{3}`" {12} {10} -whatif" -f $dirPath, $targetHost, $authType, $destDir.replace(":", "$"), $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting, $metadata.name )
                                }
                            }
                            else
                            {
                                if($packageName -ne $null)
                                {
                                    # Then perform the actual deployment.
                                    if ($hostType -eq 'Website')
                                    {
                                        $commandLine = $("msdeploy.exe -verb:sync -source:{10}=`"{0}`"{13} -dest:{11},computerName=`"{1}`",authType={2}{4} {14} -preSync:runCommand=`"{5} {6} {8} < NUL`",waitInterval={9} -postSync:runCommand=`"{5} {7} {8} < NUL`",waitInterval={9} -postSyncOnSuccess:runCommand=`"{5} Do-Finish < NUL`",waitInterval={9} {12}" -f $sourcePath, $targetHost, $authType, $destDir, $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $sourceProviderName, $destProviderName, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting )
                                    }
                                    else
                                    {
                                        $commandLine = $("msdeploy.exe -verb:sync -source:{10}=`"{0}`"{13} -dest:{11}=`"{3}`",computerName=`"{1}`",authType={2}{4} {14} -preSync:runCommand=`"{5} {6} {8} < NUL`",waitInterval={9} -postSync:runCommand=`"{5} {7} {8} < NUL`",waitInterval={9} -postSyncOnSuccess:runCommand=`"{5} Do-Finish < NUL`",waitInterval={9} {12}" -f $sourcePath, $targetHost, $authType, $destDir, $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $sourceProviderName, $destProviderName, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting )
                                    }
                                }
                                else
                                {
                                    $commandLine = $("msdeploy.exe -verb:sync -source:dirPath=`"{0}`"{11} -dest:dirPath=`"{3}`",computerName=`"{1}`",authType={2}{4} {12} -preSync:runCommand=`"{5} {6} {8} < NUL`",waitInterval={9} -postSync:runCommand=`"{5} {7} {8} < NUL`",waitInterval={9} -postSyncOnSuccess:runCommand=`"{5} Do-Finish < NUL`",waitInterval={9} {10}" -f $dirPath, $targetHost, $authType, $destDir, $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting )
                                }
                                $deployAdditionalFiles = $true
                            }
                        }
                        else
                        {
                            ## Backup all target hosts.
                            $commandLine = $("msdeploy.exe -verb:sync -source:runCommand=`"{0} Backup-Application {1} < NUL`",waitInterval={6} -dest:auto,computerName=`"{2}`",authType={3}{4} {5} " -f $powershellCommandPrefix, $flattenedArgumentList, $targetHost, $authType, $tempAgent, $msDeployLoggingSetting, $preSyncCommandWaitInterval )
                        }
                    }
                }
                
                ###########################################################################################################################
                ###########################################################################################################################
                ############################################                                      #########################################
                ############################################         EXECUTE THE COMMAND          #########################################
                ############################################                                      #########################################
                ###########################################################################################################################
                ###########################################################################################################################

                $currentCommandLine = "COMMAND-LINE: {0}" -f $commandLine
                if($useDebugLogging -eq $true -Or $checkServices -eq $true -Or $checkWebsites -eq $true -Or $checkServiceInfo -eq $true)
                {
                    Write-Host $currentCommandLine -ForegroundColor Gray
                }
                $addIndex = $commandOutput.Add($currentCommandLine)
                
                if($commandLine -ne $null -And $commandLine -ne "")
                {
                    if($checkServices -eq $true)
                    {
                        [string[]]$tempCommandOutput = Check-Service-State $serviceName $commandLine $metadata.name
                        $addIndex = $commandOutput.Add($tempCommandOutput)
                    }
                    elseif($checkWebsites -eq $true)
                    {
                        ## TODO: Implement the call to check the IIS app.
                    }
                    elseif($checkServiceInfo -eq $true)
                    {
                        [string[]]$tempCommandOutput = Check-Service-Info $serviceName $commandLine $metadata.name
                        $addIndex = $commandOutput.Add($tempCommandOutput)
                    }
                    elseif($validateDeployment -eq $true)
                    {
                        [string[]]$validationResult = (cmd.exe /C $commandLine)
                        
                        ## Verify that the command executed successfully.
                        $commandExecutionResult = $?
                        $lastExitCodeValue = $LASTEXITCODE

                        if($alwaysShowValidationResults -eq $true)
                        {
                            Write-Host $validationResult -ForegroundColor Gray
                        }
                        
                        $validationSucceeded = (Check-Validation-Results $validationResult $metadata.name $errorsList)[0]
                        
                        $addIndex = $commandOutput.Add($validationResult)
                    }
                    else
                    {
                        ## Execute the command and get the output.
                        [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)
                        $addIndex = $commandOutput.Add($tempCommandOutput)

                        ## TODO: Move the "additional files to copy" to the destination's C:\TempDeploy\nnnnnnnn\<files>, where nnnnnnnn is a random number, which will
                        ##       also be passed to the remote deploy.ps1 via a paramter: "TempAdditionalConfigsFolderName=nnnnnnnn", and is then concatenated and copied with the 
                        ##       exact tree structure to the deployment folder. Whenever "TempAdditionalConfigsFolderName=nnnnnnnn" is passed, the following function must be passed
                        ##       to the post-sync portion: "CleanupTempAdditionalConfigsFolder" which deletes the nnnnnnnn when done with the deployment.
                        ##
                        
                        ## Check if we need to copy any additional files to destination from outside the package.
                        if($deployAdditionalFiles -eq $true -And $additionalFilesToCopyArray.length -gt 0 -And $backupOnly -ne $true -And $rollback -ne $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $restartApp -ne $true)
                        {
                            $index = 0
                            foreach($additionalFileToCopy in $additionalFilesToCopyArray)
                            {
                                $additionalFileToCopyTo = $additionalFilesToCopyToArray[$index]
                                $message = "Copying {0} to {1}..." -f $additionalFileToCopy, $additionalFileToCopyTo
                                Write-Host $message
                                
                                $commandLine = $("msdeploy.exe -verb:sync -source:filePath=`"{0}`" -dest:filePath=`"{3}`",computerName=`"{1}`",authType={2}{4} {5} " -f (Join-Path $scriptPath $additionalFileToCopy), $targetHost, $authType, (Join-Path $destDir $additionalFileToCopyTo), $tempAgent, $msDeployLoggingSetting )
                                [string[]]$tempCommandOutput = (cmd.exe /C $commandLine)
                                
                                $addIndex = $commandOutput.Add($tempCommandOutput)
                                
                                $index += 1
                            }

                            ##
                            ## Restart services after copying additional config files.
                            ##
                            Remote-Restart-Service $metadata.name $targetHost $serviceName $exeName $commandOutput
                        }

                        ## Validate the deployment.
                        if($alwaysValidatePostDeployment -eq $true -And $validateDeployment -eq $false -And $backupOnly -ne $true -And $rollback -ne $true -And $bringOnline -ne $true -And $takeOffline -ne $true -And $restartApp -ne $true)
                        {
                            if($packageName -ne $null)
                            {
                                if ($hostType -eq 'Website')
                                {
                                    $addIndex = $commandOutput.Add("Validating website deployment...")
                                    $commandLine = $("msdeploy.exe -verb:sync -source:runCommand=`"{0} Validate-Web-Application {1} < NUL`",waitInterval={6} -dest:auto,computerName=`"{2}`",authType={3}{4} {5} " -f $powershellCommandPrefix, $flattenedArgumentList, $targetHost, $authType, $tempAgent, $msDeployLoggingSetting, $preSyncCommandWaitInterval )
                                }
                                else
                                {
                                    $addIndex = $commandOutput.Add("Validating application deployment...")
                                    $commandLine = $("msdeploy.exe -verb:sync -source:{10}=`"{0}`"{13} -dest:{11}=`"\\{15}\{3}`" {14} {12} -skip:skipAction=Delete,objectName=filePath,absolutePath=.*\.InstallLog$ -skip:skipAction=Delete,objectName=filePath,absolutePath=.*\.InstallState$ -whatif" -f $sourcePath, $targetHost, $authType, $destDir.replace(":", "$"), $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $sourceProviderName, $destProviderName, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting, $metadata.name )
                                }
                            }
                            else
                            {
                                $addIndex = $commandOutput.Add("Validating folder (no package) deployment...")
                                $commandLine = $("msdeploy.exe -verb:sync -source:dirPath=`"{0}`"{11} -dest:dirPath=`"\\{13}\{3}`" {12} {10} -whatif" -f $dirPath, $targetHost, $authType, $destDir.replace(":", "$"), $additionalTargetParams, $powershellCommandPrefix, $flattenedPreSyncFunctionList, $flattenedPostSyncFunctionList, $flattenedArgumentList, $preSyncCommandWaitInterval, $additionalMsDeployParameters, $additionalSourceParams, $msDeployLoggingSetting, $metadata.name )
                            }
                            
                            $addIndex = $commandOutput.Add($commandLine)
                            
                            ## Execute the validation and get the output.
                            [string[]]$validationResult = (cmd.exe /C $commandLine)
                            
                            if($alwaysShowValidationResults -eq $true)
                            {
                                Write-Host $validationResult -ForegroundColor Gray
                            }
                            $validationSucceeded = (Check-Validation-Results $validationResult $metadata.name $errorsList)[0]
                            Write-Host "validationSucceeded: $validationSucceeded."
                            
                            ## TODO: Also validate the configs being copied from outside of the package.
                            ##       Those can be validated remotely for both Websites and Windows Services since they are individual files provided outside of the deployment package.
                            ##
                            
                            $addIndex = $commandOutput.Add($validationResult)
                        }                
                    }
                } ## if($commandLine -ne "")
            } ## if($preInstallValidationSucceeded -eq $true -And $backupValidationSucceeded -eq $true)

            ########################################################################################
            ##                                                                                    ##
            ##                             Write validation status.                               ##
            ##                                                                                    ##
            ########################################################################################

            if($checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
            {
                if($validationSucceeded -eq $true -And $preInstallValidationSucceeded -eq $true -And $backupValidationSucceeded -eq $true)
                {
                    ## Set the current target host to 'Succeeded':
                    move-item -path $deploymentStatusInProgressFilePath -destination $deploymentStatusSucceededFilePath -errorAction SilentlyContinue
                }
                else
                {
                    ## Set the current target host to 'Failed':
                    move-item -path $deploymentStatusInProgressFilePath -destination $deploymentStatusFailedFilePath -errorAction SilentlyContinue
                }
            }

            ########################################################################################
            ##                                                                                    ##
            ##                             Write Output to Log.                                   ##
            ##                                                                                    ##
            ########################################################################################

            if($commandOutput.Count -gt 0 -And $checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
            {
                Write-To-Log $deploymentLogFilePath $commandOutput $errorsList $serviceAccountPassword
            }

            Write-Host ("Host: {0} - Done." -f $metadata.name) -BackgroundColor DarkGreen -ForegroundColor White
        }
        catch
        {
            if($checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
            {
                ## Set the current target host to 'Failed':
                move-item -path $deploymentStatusInProgressFilePath -destination $deploymentStatusFailedFilePath -errorAction SilentlyContinue
            }
            
            $logMessage = "ERROR encountered while deploying to target host: $_"
            Write-Host $logMessage -BackgroundColor Red -ForegroundColor White
            
            $errorMessage = "ERROR:[Deploy-To-TargetHost]: {0}" -f $logMessage
            $addIndex = $errorsList.Add($errorMessage)
        }

        ########################################################################################
        ##                                                                                    ##
        ##                             Write Stats Log File.                                  ##
        ##                                                                                    ##
        ########################################################################################

        if($checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
        {
            $logMessage = ("End Time: {0}" -f (get-date))
            $logMessage | Add-Content $logFolderStatsFilePath
        }

        ########################################################################################
        ##                                                                                    ##
        ##                             Write Errors Log File.                                 ##
        ##                                                                                    ##
        ########################################################################################

        if($errorsList.count -gt 0)
        {
            if($checkServices -ne $true -And $checkWebsites -ne $true -And $checkServiceInfo -ne $true)
            {
                $errorsList | Set-Content $deploymentErrorsFilePath
                Write-Host "Done writing errors log."
            }
            else
            {
                Write-Host "Errors Encountered:" -BackgroundColor Red -ForegroundColor White
                foreach($errorItem in $errorsList)
                {
                    Write-Host $errorsItem
                }
            }
        }
    }
}

###########################################################################################################################
###########################################################################################################################
############################################                                      #########################################
############################################          UTILITIES, ETC...           #########################################
############################################                                      #########################################
###########################################################################################################################
###########################################################################################################################


function Check-Validation-Results()
{
    param([string[]]$result, [string]$hostNameToValidate, [system.collections.arraylist]$errorsList)
    
    if(($result -contains $successValidationPattern1) -eq $true -Or ($result -contains $successValidationPattern2) -eq $true)
    {
        $true
        
        $logMessage = "Validation [{0}]: SUCCESS" -f $hostNameToValidate
        Write-Host $logMessage -ForegroundColor Green
        
        0
    }
    else
    {
        $false
        
        $logMessage = "Validation [{0}]: FAILED" -f $hostNameToValidate
        Write-Host $logMessage -ForegroundColor Red
        
        $errorMessage = "ERROR:[Deploy-To-TargetHost]: {0}" -f $logMessage
        ##$addIndex = $errorsList.Add($errorMessage)
        $errorsList.Add($errorMessage)
    }
}

function Remote-Restart-Service()
{
    param([string]$hostName, [string]$targetHost, [string]$serviceName, [string] $exeName, [system.collections.arraylist]$commandOutput)
    
    ## Stop the service.
    Write-Host "Inside deploy.ps1::Remote-Restart-Service: Stopping service '$serviceName' on machine '$targetHost'..."

    ## Check if process exists before attempting to stop the service.
    $serviceIsRunning = $false
    $servicePrior = Get-Service -name "$ServiceName" -computername $hostName -errorAction SilentlyContinue
    if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
    {
        Write-Host ("Service State: {0}" -f $servicePrior.status)
        
        if($servicePrior.status -eq "Running")
        {
            $serviceIsRunning = $true
        }
    }
    
    if($serviceIsRunning -eq $true)
    {
        Write-Host "Service was found running. A stop signal will be sent."

        $commandLine = $("SC `"\\{0}`" STOP `"{1}`"" -f $hostName, $serviceName)
        $tempCommandOutput = cmd.exe /C $commandLine
        $addIndex = $commandOutput.Add($tempCommandOutput)
        
        ## Wait for the service to stop.
        Write-Host "Inside deploy.ps1::Remote-Restart-Service: Waiting for $defaultServiceWaitInMilliseconds milliseconds..."
        
        $serviceIsStopped = $false
        $iterationCounter = $defaultNumberOfProcessWaitBeforeStartIterations - 1
        if($iterationCounter -lt 0)
        {
            $iterationCounter = 0
        }
        
        do
        {
            Write-Host "Iteration: $iterationCounter"
            
            $servicePrior = Get-Service -name "$ServiceName" -computername $hostName -errorAction SilentlyContinue
            if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
            {
                Write-Host ("Service State: {0}" -f $servicePrior.status)
                
                if($servicePrior.status -eq "Stopped")
                {
                    $serviceIsStopped = $true
                }
            }

            if($serviceIsStopped -eq $false)
            {
                Write-Host "Service has not stopped yet. Waiting for $defaultServiceWaitInMilliseconds milliseconds..."
                Start-Sleep -m $defaultServiceWaitInMilliseconds
            }
            else
            {
                Write-Host "Service has stopped."
            }
        } until ($iterationCounter-- -le 0 -Or $serviceIsStopped -eq $true)

    }
    else
    {
        Write-Host "Service was not found running. No stopping is necessary."
    }

    ## Start the service.
    Write-Host "Inside deploy.ps1::Remote-Restart-Service: Starting service '$serviceName' on machine '$targetHost'..."
    $commandLine = $("SC `"\\{0}`" START `"{1}`"" -f $hostName, $serviceName)
    $tempCommandOutput = cmd.exe /C $commandLine
    $addIndex = $commandOutput.Add($tempCommandOutput)
    
    return $commandOutput
}

function Validate-Web-Application()
{
    $ServiceName = GetArgumentValue("ServiceName")
    $ServicePath = GetArgumentValue("ServicePath")
    $PackageNameOnly = GetArgumentValue("PackageNameOnly")

    Write-Host "Inside deploy.ps1::Validate-Web-Application: Validating web application '$ServiceName'..."

    ##
    ## Add msdeploy to the Path environment variable on the target host machine.
    ##
    if([System.IO.Directory]::Exists($msdeployV3Path))
    {
        $env:path = $env:path + ";" + $msdeployV3Path
    }
    elseif([System.IO.Directory]::Exists($msdeployV2Path))
    {
        $env:path = $env:path + ";" + $msdeployV2Path
    }
    else
    {
        if([System.IO.Directory]::Exists($msdeployPath))
        {
            $env:path = $env:path + ";" + $msdeployPath
        }
        else
        {
            Write-Host "ERROR:[Deploy-To-TargetHost]: MSDeploy does not seem to be installed on this machine. Could not find '$msdeployPath', '$msdeployV2Path', or '$msdeployV3Path'." -BackgroundColor Red -ForegroundColor White
            return
        }
    }

    $sourceProviderName = "package"
    $destProviderName = "auto"
    $sourcePath = (Join-Path (Join-Path $remoteTempToolsPath $ServiceName) $PackageNameOnly)
    
    $webCommandLine = $("msdeploy.exe -verb:sync -source:{2}=`"{0}`" -dest:{3} {4} {5} -whatif" -f $sourcePath, $ServicePath, $sourceProviderName, $destProviderName, $additionalMsDeployParametersForWebsiteValidation, $defaultAdditionalMsDeployParametersForWebsites )
    Write-Host $webCommandLine
    [string[]]$commandOutput = (cmd /C $webCommandLine)
    Write-Host $commandOutput -separator "`n"
}

Add-Man "Backup-Application" "Backs up the application before deploying a new version." "Deployment"
function Backup-Application()
{
    $ServicePath = GetArgumentValue("ServicePath")
    $RootBackupDir = GetArgumentValue("RootBackupDir")
    $BackupDir = GetArgumentValue("BackupDir")
    $OldExeFullPath = GetArgumentValue("OldExeFullPath")
    
    Write-Host "Inside deploy.ps1::Backup-Application: Copying '$ServicePath' to '$BackupDir'..."
    
    ## This will copy the source folder ($ServicePath) and all its contents recursively to the destination folder ($BackupDir).
    ## If any part of the destination folder does not exist, it will be created, so there is no need for us to check for existence
    ## of the parent folder and to explicitly create it. However, we have noticed that this is not always the case and that sometimes
    ## the folder tree is not created. So, we added the additional checks...
    
    try
    {
        if([System.IO.Directory]::Exists($ServicePath) -eq $true)
        {
            if([System.IO.Directory]::Exists($RootBackupDir) -ne $true)
            {
                ## Create backups folder tree.
                new-item -itemtype directory -path $RootBackupDir
            }

            if([System.IO.Directory]::Exists($RootBackupDir) -eq $true)
            {
                copy-item $ServicePath -destination $BackupDir -recurse -force
            
                if([System.IO.Directory]::Exists($BackupDir) -eq $true)
                {
                    Write-Host "Inside deploy.ps1::Backup-Application: Backup completed successfully."
                }
                else
                {
                    Write-Host "ERROR:[Backup-Application]: Failed to create backup directory '$BackupDir'. Directory not found after copying ended." -foregroundcolor red
                    cmd.exe /C exit 108
                }
            }
            else
            {
                Write-Host "ERROR:[Backup-Application]: Failed to create root backup directory '$RootBackupDir'. Directory not found after creation." -foregroundcolor red
                cmd.exe /C exit 109
            }
        }
        else
        {
            ## Bail out if the application directory does not exist (first time install):
            Write-Host "Inside deploy.ps1::Backup-Application: Backup was skipped as application path does not exist: $ServicePath."
        }
    }
    catch
    {
        Write-Host "ERROR:[Backup-Application]: Encountered error(s) while backing up service from '$ServicePath': $_" -foregroundcolor red
        cmd.exe /C exit 101
    }
    
    ## If old service exists, back it up next.
    ## Since the old service folder is never removed by this script, there is no need to rollback from the backup.
    if($OldExeFullPath -ne $null -And $OldExeFullPath -ne "" -And [System.IO.Directory]::Exists($OldExeFullPath))
    {        
        try
        {
            $OldServiceBackupDir = "{0}-OldService" -f $BackupDir
            $OldServiceFolderPath = Split-Path -parent $OldExeFullPath

            Write-Host "Inside deploy.ps1::Backup-Application: Copying old service from '$OldServiceFolderPath' to '$OldServiceBackupDir'..."
            
            copy-item $OldServiceFolderPath -destination $OldServiceBackupDir -recurse -force
            
            Write-Host "Inside deploy.ps1::Backup-Application: Successfully backed up old service from '$OldServiceFolderPath'." -foregroundcolor green
        }
        catch
        {
            Write-Host "ERROR:[Backup-Application]: Encountered error(s) while backing up old service from '$OldExeFullPath': $_" -foregroundcolor red
            cmd.exe /C exit 102
        }
    }
}

Add-Man "Remove-ReadOnly" "Removes the read-only attribute from all files/folders under the application folder." "Deployment"
function Remove-ReadOnly()
{
    $ServicePath = GetArgumentValue("ServicePath")
    
    Write-Host "Inside deploy.ps1::Remove-ReadOnly: Stripping read-only flag from '$ServicePath'..."
    
    if([System.IO.Directory]::Exists($ServicePath))
    {
        $command = $("ATTRIB -R `"{0}\*.*`" /S /D" -f $ServicePath)
        Write-Host "Inside deploy.ps1::Remove-ReadOnly: COMMAND: $command."
        CMD.EXE /C $command
        
        if($? -eq $false)
        {
            Write-Host "ERROR:[Remove-ReadOnly]: Attrib -r failed with exit code: $LASTEXITCODE." -foregroundcolor red
        }
        else
        {
            Write-Host "Inside deploy.ps1::Remove-ReadOnly: Successfully removed read-only attribute from '$ServicePath'." -foregroundcolor green
        }
    }
    else
    {
        Write-Host "Inside deploy.ps1::Remove-ReadOnly: Skipped removal of read-only attribute from '$ServicePath'. Folder does not exist." -foregroundcolor yellow
    }    
}

Add-Man "Remove-Hidden" "Removes the hidden attribute from all files/folders under the application folder." "Deployment"
function Remove-Hidden()
{
    $ServicePath = GetArgumentValue("ServicePath")
    
    Write-Host "Inside deploy.ps1::Remove-Hidden: Stripping hidden flag from '$ServicePath'..."
    
    if([System.IO.Directory]::Exists($ServicePath))
    {
        $command = $("ATTRIB -H `"{0}\*.*`" /S /D" -f $ServicePath)
        Write-Host "Inside deploy.ps1::Remove-Hidden: COMMAND: $command."
        CMD.EXE /C $command
        
        if($? -eq $false)
        {
            Write-Host "ERROR:[Remove-Hidden]: Attrib -h failed with exit code: $LASTEXITCODE." -foregroundcolor red
        }
        else
        {
            Write-Host "Inside deploy.ps1::Remove-Hidden: Successfully removed hidden attribute from '$ServicePath'." -foregroundcolor green
        }
    }
    else
    {
        Write-Host "Inside deploy.ps1::Remove-Hidden: Skipped removal of hidden attribute from '$ServicePath'. Folder does not exist." -foregroundcolor yellow
    }    
}

Add-Man "Kill-ServicesMsc" "Kills services.msc if it is open." "Deployment"
function Kill-ServicesMsc()
{
    Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Checking if mmc.exe is alive..."
    
    $process = get-wmiobject win32_process -filter "name='mmc.exe'"
    if($process)
    {
        Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Process was found: mmc.exe."
        
        [string]$commandLine = $process.CommandLine
        
        Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Command Line: $commandLine."
        
        if($commandLine -like "*\system32\services.msc*")
        {
            Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Process has command line signature for services.msc: $commandLine."
            ##$process
            $p = get-process -id $process.Handle
            stop-process -force -inputobject $p
        }
        else
        {
            Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Process does not have command line signature."
        }
    }
    else
    {
        Write-Host "Inside deploy.ps1::Kill-ServicesMsc: Process was NOT found."
    }
}

Add-Man "Rollback-To-LKG" "Copies the last-known-good (LKG) version of the app back to the application folder from the backups folder." "Deployment"
function Rollback-To-LKG()
{
    ## LKG folder:
    $ServicePath = GetArgumentValue("ServicePath")
    $BackupDir = GetArgumentValue("BackupDir")
    $OldExeFullPath = GetArgumentValue("OldExeFullPath")
    
    Write-Host "Inside deploy.ps1::Rollback-To-LKG: Copying LKG folder to '$ServicePath'..."
    
    $backupRootDir = Split-Path -parent $BackupDir
    $backupRootDirInfo = New-Object System.IO.DirectoryInfo($backupRootDir)
    $backupFolders = $backupRootDirInfo.GetDirectories("Pre-*",[System.IO.SearchOption]::TopDirectoryOnly) | sort-object -property CreationTime -descending
    
    ## Proceed to roll back only if backup folder are found.
    if($backupFolders.length -gt 0)
    {
        $LKGBackupDir = $backupFolders[0].FullName        ## Get the last folder (topmost since we sorted the folders in descending order).
        if($LKGBackupDir -ne $null -And $LKGBackupDir -ne "")
        {
            Write-Host "Inside deploy.ps1::Rollback-To-LKG: LKG Folder: '$LKGBackupDir'."

            $destinationFolder = $ServicePath
            
            if($OldExeFullPath -ne $null -And $OldExeFullPath -ne "" -And ($LKGBackupDir -match ".*-OldService$"))
            {
                ## Restore the old service since that was the last thing we were replacing.
                $destinationFolder = Split-Path -parent $OldExeFullPath
                Write-Host "Inside deploy.ps1::Rollback-To-LKG: LKG is an old service. Restoring to '$destinationFolder'..."
            }

            ## Delete existing folder items first.
            Write-Host "Inside deploy.ps1::Rollback-To-LKG: Deleting contents of '$destinationFolder'..."
            remove-item ($destinationFolder + "\*") -recurse -force
            
            ## Copy LKG files into empty folder.
            Write-Host "Inside deploy.ps1::Rollback-To-LKG: Copying LKG from '$LKGBackupDir' to '$destinationFolder'..."
            copy-item ($LKGBackupDir + "\*") -destination $destinationFolder -recurse -force
        }
        else
        {
            Write-Host "ERROR:[Rollback-To-LKG]: Could not roll back. Backup folder name is empty." -foregroundcolor red
        }
    }
    else
    {
        Write-Host "ERROR:[Rollback-To-LKG]: Could not roll back. No backup folders found." -foregroundcolor red
    }
    
    Write-Host "Inside deploy.ps1::Rollback-To-LKG: Finished rolling back to LKG."
}

Add-Man "Unblock-Binary" "Uses streams.exe to unblock a binary. Should not be necessary with deployments executed via this script." "Deployment"
function Unblock-Binary()
{
    $ExeName = GetArgumentValue("ExeName")
    $ServicePath = GetArgumentValue("ServicePath")
    
    Write-Host "Unblocking $ExeName..." 

    ##
    ## C:\>streams.exe /?
    ## 
    ## Streams v1.56 - Enumerate alternate NTFS data streams
    ## Copyright (C) 1999-2007 Mark Russinovich
    ## Sysinternals - www.sysinternals.com
    ##
    ## usage: streams.exe [-s] [-d] <file or directory>
    ## -s     Recurse subdirectories
    ## -d     Delete streams
    ##
    
    try
    {
        "{0} /accepteula -d {1}" -f (Join-Path $remoteTempToolsPath $streamsFileName), (Join-Path $ServicePath $ExeName)
        
        Write-Host "Successfully unblocked $ExeName." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Unblock-Binary]: Encountered error(s) while unblocking $ExeName : $_" -foregroundcolor red
        #throw
    }
}

####################################################
##                                                ##
##          Windows Services Utilities.           ##
##                                                ##
####################################################
Add-Man "Start-Service" "Starts a Windows service." "Deployment"
function Start-Service() 
{ 
    $ServiceName = GetArgumentValue("ServiceName")
    $OldServiceName = GetArgumentValue("OldServiceName")
    $IsRollback = GetArgumentValue("IsRollback")
    $SkipServiceStart = GetArgumentValue("SkipServiceStart")
    
    if($SkipServiceStart -ne "true")
    {
        Write-Host "Starting service '$ServiceName'..." 

        $servicePrior = $null
        $retriesCount = $numberOfServiceStartRetriesPostDeployment
        if($retriesCount -lt 1)
        {
            $retriesCount = 1
        }
        
        for($i=0; $i -lt $retriesCount; $i++)
        {
            try
            {
                if($OldServiceName -ne $null -And $OldServiceName -ne "" -And $IsRollback -eq "true")
                {
                    Write-Host "IsRollback=true: Starting old service '$OldServiceName'..." 
                    $ServiceName = $OldServiceName
                }

                $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
                if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                {
                    Write-Host $servicePrior.status
                    Write-Host "Setting startup type to 'Automatic' for '$ServiceName'..." 
                    Set-Service "$ServiceName" -startuptype automatic -errorAction SilentlyContinue
                    
                    if($servicePrior.status -eq "Running")
                    {
                        Write-Host "Skipped starting service '$ServiceName' since it is already in RUNNING state." -foregroundcolor green
                    }
                    else
                    {
                        Write-Host "Attempting to start '$ServiceName'..." 
                        ### Different possible variations for starting a service.
                        # Invoke-Command -ScriptBlock {param($servicename) Start-Service $servicename} -Args $ServiceName 
                        # Start-Service $ServiceName
                        # sc start $ServiceName  
                        net start $ServiceName  

                        $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
                        if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                        {
                            Write-Host $servicePrior.status
                            
                            if($servicePrior.status -eq "Running")
                            {
                                Write-Host "Successfully started service '$ServiceName'." -foregroundcolor green
                                break
                            }
                            else
                            {
                                if($i -lt ($retriesCount -1))
                                {
                                    Write-Host "WARNING:[Start-Service]: Failed to start '$ServiceName'. Will wait $defaultServiceWaitInMilliseconds before trying again." -foregroundcolor yellow
                                    Start-Sleep -m $defaultServiceWaitInMilliseconds
                                }
                            }
                        }
                        else
                        {
                            Write-Host "ERROR:[Start-Service]: Failed to get the status of service '$ServiceName' after starting it." -foregroundcolor red
                        }
                    }
                }
                else
                {
                    Write-Host "ERROR:[Start-Service]: Failed to get the status of service '$ServiceName' prior to starting it. The service might not have been installed correctly." -foregroundcolor red
                }
            }
            catch
            {
                Write-Host "ERROR:[Start-Service]: Encountered error(s) while starting service '$ServiceName': $_" -foregroundcolor red
                #throw
            }
        }

        if($servicePrior -ne $null -And $servicePrior.status -ne $null -And $servicePrior.status -ne "Running")
        {
            $message = "ERROR:[Start-Service]: Failed to start '{0}'. Status: {1}." -f $ServiceName, $servicePrior.status
            Write-Host $message -foregroundcolor red
        }
    }
    else
    {
        Write-Host "Skipped starting service '$ServiceName'. since 'SkipServiceStart'=true." 
    }
} 

Add-Man "Stop-Service" "Stops a Windows service." "Deployment"
function Stop-Service() 
{ 
    $ServiceName = GetArgumentValue("ServiceName")
    $OldServiceName = GetArgumentValue("OldServiceName")

    if($OldServiceName -ne $null -And $OldServiceName -ne "")
    {
        Write-Host "Stopping old service '$OldServiceName'..."
        
        try
        {
            $servicePrior = Get-Service "$OldServiceName" -errorAction SilentlyContinue
            if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
            {
                Write-Host $servicePrior.status
                
                if ($servicePrior.status -ne "Stopped" -And $servicePrior.status -ne "Stopping")
                {
                    Write-Host "Attempting to force stopping of old service '$OldServiceName'..." 
                    ### Different possible variations for stopping a service.
                    # Invoke-Command -ScriptBlock {param($servicename) Stop-Service $servicename} -Args $OldServiceName 
                    # Stop-Service $OldServiceName -force
                    # sc stop $OldServiceName  
                    net stop "$OldServiceName" 

                    $servicePrior = Get-Service "$OldServiceName" -errorAction SilentlyContinue
                    if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                    {
                        Write-Host $servicePrior.status
                        Write-Host "Successfully stopped ol service '$OldServiceName'." -foregroundcolor green
                    }
                    else
                    {
                        Write-Host "ERROR:[Stop-Service]: Failed to get the status of old service '$OldServiceName' after stopping it." -foregroundcolor red
                    }
                }
                else
                {
                    Write-Host "Skipping stopping of old service '$OldServiceName' since it is already stopped."
                }
            }
            else
            {
                Write-Host "WARNING: Failed to get the status of old service '$OldServiceName' prior to stopping it. This might be due to that it is being installed for the first time." -foregroundcolor yellow
            }
        }
        catch
        {
            Write-Host "ERROR:[Stop-Service]: Encountered error(s) while stopping old service $OldServiceName : $_" -foregroundcolor red
        }
    }
        
    Write-Host "Stopping service '$ServiceName'..." 
    
    try
    {
        $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
        if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
        {
            Write-Host $servicePrior.status
            
            if ($servicePrior.status -ne "Stopped" -And $servicePrior.status -ne "Stopping")
            {
                Write-Host "Attempting to force stopping of service '$ServiceName'..." 
                ### Different possible variations for stopping a service.
                # Invoke-Command -ScriptBlock {param($servicename) Stop-Service $servicename} -Args $ServiceName 
                # Stop-Service $ServiceName -force
                # sc stop $ServiceName  
                net stop $ServiceName  

                $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
                if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
                {
                    Write-Host $servicePrior.status
                    Write-Host "Successfully stopped service '$ServiceName'." -foregroundcolor green
                }
                else
                {
                    Write-Host "ERROR:[Stop-Service]: Failed to get the status of service '$ServiceName' after stopping it." -foregroundcolor red
                }
            }
            else
            {
                Write-Host "Skipping stopping of service '$ServiceName' since it is already stopped."
            }
        }
        else
        {
            Write-Host "WARNING: Failed to get the status of service '$ServiceName' prior to stopping it. This might be due to that it is being installed for the first time." -foregroundcolor yellow
        }
    }
    catch
    {
        Write-Host "ERROR:[Stop-Service]: Encountered error(s) while stopping service '$ServiceName': $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Restart-Service" "Restarts a Windows service." "Deployment"
function Restart-Service() 
{ 
    $ServiceName = GetArgumentValue("ServiceName")
    
    Write-Host "Restarting service '$ServiceName'..." 

    try
    {
        $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
        if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
        {
            Write-Host $servicePrior.status
            
            Write-Host "Attempting to restart '$ServiceName'..." 

            Write-Host "Step 1: Stopping '$ServiceName'..."
            net stop $ServiceName
            
            Start-Sleep -m $defaultServiceWaitInMilliseconds 
            
            Write-Host "Step 2: Starting '$ServiceName'..."
            net start $ServiceName  

            $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
            if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
            {
                Write-Host $servicePrior.status
                Write-Host "Successfully restarted service '$ServiceName'." -foregroundcolor green
            }
            else
            {
                Write-Host "ERROR:[Restart-Service]: Failed to get the status of service '$ServiceName' after restarting it." -foregroundcolor red
            }
        }
        else
        {
            Write-Host "ERROR:[Restart-Service]: Failed to get the status of service '$ServiceName' prior to restarting it. The service might not have been installed correctly." -foregroundcolor red
        }
    }
    catch
    {
        Write-Host "ERROR:[Restart-Service]: Encountered error(s) while restarting service '$ServiceName': $_" -foregroundcolor red
        #throw
    }
} 

Add-Man "Uninstall-Service" "Uninstalls a Windows service as part of a new deployment." "Deployment"
function Uninstall-Service() 
{ 
    $ServiceName = GetArgumentValue("ServiceName")
    $ExeName = GetArgumentValue("ExeName")
    $ServicePath = GetArgumentValue("ServicePath")
    $OldExeFullPath = GetArgumentValue("OldExeFullPath")
    
    if($OldExeFullPath -ne $null -And $OldExeFullPath -ne "")
    {
        Write-Host "Uninstalling old service from path $OldExeFullPath..."
        
        try
        {
            cmd /c "$defaultInstallUtilPath" /uninstall "$OldExeFullPath"
            
            Write-Host "Successfully uninstalled old service from '$OldExeFullPath'." -foregroundcolor green
        }
        catch
        {
            Write-Host "ERROR:[Uninstall-Service]: Encountered error(s) while uninstalling old service from '$OldExeFullPath': $_" -foregroundcolor red
        }
    }

    Write-Host "Uninstalling service $ServiceName from path '$ServicePath'..." 

    try
    {
        $fullExePath = Join-Path $ServicePath $ExeName
        cmd /c "$defaultInstallUtilPath" /uninstall "$fullExePath"
        
        Write-Host "Successfully uninstalled service '$ServiceName'." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Uninstall-Service]: Encountered error(s) while uninstalling service $ServiceName : $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Install-Service" "Installs a Windows service as part of a new deployment." "Deployment"
function Install-Service() 
{ 
    $ServiceName = GetArgumentValue("ServiceName")
    $ExeName = GetArgumentValue("ExeName")
    $ServicePath = GetArgumentValue("ServicePath")
    $ServiceAccountUserName = GetArgumentValue("ServiceAccountUserName")
    $ServiceAccountPassword = GetArgumentValue("ServiceAccountPassword")
    $OldExeFullPath = GetArgumentValue("OldExeFullPath")
    $OldServiceName = GetArgumentValue("OldServiceName")
    $IsRollback = GetArgumentValue("IsRollback")
    $ServiceStartOption = GetArgumentValue("ServiceStartOption") ## e.g: auto
    $ServiceFailureResetPeriod = GetArgumentValue("ServiceFailureResetPeriod") ## e.g.: 60000
    $ServiceFailureActions = GetArgumentValue("ServiceFailureActions") ## e.g.: restart/60000/restart/6000/restart/60000

    Write-Host "Installing service '$ServiceName' from path '$ServicePath'..." 

    ## Fill in any missing values:
    if($ServiceAccountUserName -eq $null -Or $ServiceAccountUserName -eq "")
    {
        $ServiceAccountUserName = $defaultServiceAccountName
    }
    if($ServiceStartOption -eq $null -Or $ServiceStartOption -eq "")
    {
        $ServiceStartOption = $defaultServiceStartOption
    }
    if($ServiceFailureResetPeriod -eq $null -Or $ServiceFailureResetPeriod -eq "")
    {
        $ServiceFailureResetPeriod = $defaultServiceFailureResetPeriod
    }
    if($ServiceFailureActions -eq $null -Or $ServiceFailureActions -eq "")
    {
        $ServiceFailureActions = $defaultServiceFailureActions
    }
    
    try
    {
        $fullExePath = Join-Path $ServicePath $ExeName
        if($OldExeFullPath -ne $null -And $OldExeFullPath -ne "" -And $IsRollback -eq "true")
        {
            $fullExePath = $OldExeFullPath
            $ServiceName = $OldServiceName
            Write-Host "IsRollback=true: Installing old service '$ServiceName' from path '$fullExePath'..." 
        }
        
        cmd /c "$defaultInstallUtilPath" /install "$fullExePath"

        Write-Host "Successfully installed service '$ServiceName'." -foregroundcolor green
        
        ## Set the service account and other service settings.
        $servicePrior = Get-Service "$ServiceName" -errorAction SilentlyContinue
        if ($servicePrior -ne $null -And $servicePrior.status -ne $null)
        {
            Write-Host $servicePrior.status
            Write-Host "Setting service account to '$ServiceAccountUserName' and setting startup type to '$ServiceStartOption' for '$ServiceName'..." 
                    
            cmd /C SC config "$ServiceName" obj= "$ServiceAccountUserName"
            cmd /C SC failure "$ServiceName" reset= "$ServiceFailureResetPeriod" actions= "$ServiceFailureActions"
            cmd /C SC config "$ServiceName" start= "$ServiceStartOption"

            ##Set-Service $ServiceName -startuptype automatic -errorAction SilentlyContinue

            Write-Host "Successfully set service account and settings for service '$ServiceName'." -foregroundcolor green
        }
        else
        {
            Write-Host "ERROR:[Install-Service]: Failed to get the status of service '$ServiceName' after installing it. The service might not have been installed correctly. Please verify that the service settings before starting it." -foregroundcolor red
        }
    }
    catch
    {
        Write-Host "ERROR:[Install-Service]: Encountered error(s) while installing service '$ServiceName': $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Check-Service-State" "Checks and reports the state of a Windows service." "Deployment"
function Check-Service-State()
{
    param([string]$servicename, [string]$command, [string]$hostname)
    
    #Write-Host ("Host: {0} - Checking status of '{1}' service..." -f $hostname, $servicename)

    ## Pattern used in parsing the service query output to find the service state.
    $pattern = '^\s*STATE[^:]*:\s*\d+\s*(?<Service_State>.*)$'

    if ((cmd /C $command)[3] -match $pattern -eq $true)
    {
        $match = $matches.Service_State.trim()
        if($match -eq "running")
        {
            Write-Host "Service State is RUNNING" -BackgroundColor Green -ForegroundColor White
        }
        else
        {
            Write-Host ("Service State: {0}" -f $match) -BackgroundColor Yellow -ForegroundColor Red
        }
    }
    else
    {
        Write-Host "Could not determine service state for '$servicename'" -BackgroundColor Red -ForegroundColor White
    }
}

Add-Man "Check-Service-Info" "Checks and reports basic information about a Windows service." "Deployment"
function Check-Service-Info()
{
    param([string]$servicename, [string]$command, [string]$hostname)
    
    #Write-Host ("Host: {0} - Gathering information on '{1}' service..." -f $hostname, $servicename)

    $identityPattern = '^\s*SERVICE_START_NAME[^:]*:\s*(?<Service_Identity>.*)$'
    $startTypePattern = '^\s*START_TYPE[^:]*:\s*\d+\s*(?<Start_Type>.*)$'
    $displayNamePattern = '^\s*DISPLAY_NAME[^:]*:\s*(?<Display_Name>.*)$'
    $binaryPathNamePattern = '^\s*BINARY_PATH_NAME[^:]*:\s*(?<Binary_Path_Name>.*)$'
    
    [string[]]$commandOutput = (cmd /C $command)

    ## Service_Identity
    if ($commandOutput[11] -match $identityPattern -eq $true)
    {
        $match = $matches.Service_Identity.trim()
        Write-Host ("Service Identity: {0}" -f $match) -BackgroundColor White -ForegroundColor Red
    }
    else
    {
        Write-Host "Could not get 'Service Identity' for '$servicename'" -BackgroundColor Red -ForegroundColor White
    }
    
    ## Start_Type
    if ($commandOutput[4] -match $startTypePattern -eq $true)
    {
        $match = $matches.Start_Type.trim()
        Write-Host ("Start Type: {0}" -f $match) -BackgroundColor White -ForegroundColor Red
    }
    else
    {
        Write-Host "Could not get 'Start Type' for '$servicename'" -BackgroundColor Red -ForegroundColor White
    }

    ## Display_Name
    if ($commandOutput[9] -match $displayNamePattern -eq $true)
    {
        $match = $matches.Display_Name.trim()
        Write-Host ("Display Name: {0}" -f $match) -BackgroundColor White -ForegroundColor Red
    }
    else
    {
        Write-Host "Could not get 'Display Name' for '$servicename'" -BackgroundColor Red -ForegroundColor White
    }

    ## Binary_Path_Name
    <#
    if ($commandOutput[6] -match $binaryPathNamePattern -eq $true)
    {
        $match = $matches.Binary_Path_Name.trim()
        Write-Host ("Binary Path Name: {0}" -f $match) -BackgroundColor White -ForegroundColor Red
    }
    else
    {
        Write-Host "Could not get 'Binary Path Name' for '$servicename'" -BackgroundColor Red -ForegroundColor White
    }
    #>
}

####################################################
##                                                ##
##               Website Utilities.               ##
##                                                ##
####################################################
Add-Man "Start-AppPool" "Starts an IIS App Pool." "Deployment"
function Start-AppPool()
{
    $IisApplicationPoolName = GetArgumentValue("IisApplicationPoolName")

    Write-Host "Starting App Pool '$IisApplicationPoolName'..." 

    try
    {
        Start-IisAppPool "$IisApplicationPoolName"
        
        Write-Host "Successfully started App Pool." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Start-AppPool]: Encountered error(s) while starting App Pool: $_" -foregroundcolor red
        #throw
    }

}

Add-Man "Start-Website" "Starts a website." "Deployment"
function Start-Website()
{
    Write-Host "Starting IIS..."

    try
    {
        IISRESET /START

        Write-Host "Successfully started IIS." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Start-Website]: Encountered error(s) while starting IIS: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Stop-AppPool" "Stops an IIS App Pool." "Deployment"
function Stop-AppPool()
{
    $IisApplicationPoolName = GetArgumentValue("IisApplicationPoolName")

    Write-Host "Stopping App Pool '$IisApplicationPoolName'..." 

    try
    {
        Stop-IisAppPool "$IisApplicationPoolName"

        Write-Host "Successfully stopped App Pool." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Stop-AppPool]: Encountered error(s) while stopping App Pool: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Stop-Website" "Stops a website." "Deployment"
function Stop-Website()
{
    Write-Host "Stopping IIS..."

    try
    {
        IISRESET /STOP

        Write-Host "Successfully stopped IIS." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Stop-Website]: Encountered error(s) while stopping IIS: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Restart-AppPool" "Restarts an IIS App Pool." "Deployment"
function Restart-AppPool()
{
    $IisApplicationPoolName = GetArgumentValue("IisApplicationPoolName")

    Write-Host "Restarting App Pool '$IisApplicationPoolName'..." 

    try
    {
        Recycle-IisAppPool "$IisApplicationPoolName"

        Write-Host "Successfully restarted App Pool." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Restart-AppPool]: Encountered error(s) while restarting App Pool: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Restart-Website" "Restarts a website." "Deployment"
function Restart-Website()
{
    Write-Host "Restarting IIS..."

    try
    {
        IISRESET /RESTART

        Write-Host "Successfully restarted IIS." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[Restart-Website]: Encountered error(s) while restarting IIS: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "List-AppPools" "Lists the IIS App Pools available on the machine." "Deployment"
function List-AppPools()
{
    Write-Host "Listing App Pools..." 

    try
    {
        Get-IisAppPools

        Write-Host "Successfully listed App Pools." -foregroundcolor green
    }
    catch
    {
        Write-Host "ERROR:[List-AppPools]: Encountered error(s) while listing App Pools: $_" -foregroundcolor red
        #throw
    }
}

Add-Man "Take-Website-Offline" "Takes a website offline by copying the App_Offline.htm document to the website's root." "Deployment"
function Take-Website-Offline()
{
    $ServicePath = GetArgumentValue("ServicePath")
    
    Write-Host "Inside deploy.ps1::Take-Website-Offline: Copying App_Offline.htm from '$remoteTempToolsPath' to '$ServicePath'..."
    
    copy-item (Join-Path $remoteTempToolsPath $appOfflineDocFileName) -destination (Join-Path $ServicePath $appOfflineDocFileName) -force
    
    Write-Host "Inside deploy.ps1::Take-Website-Offline: Operation completed successfully."
}

Add-Man "Bring-Website-Online" "Brings a website online by deleting the App_Offline.htm document from the website's root." "Deployment"
function Bring-Website-Online()
{
    $ServicePath = GetArgumentValue("ServicePath")
    
    Write-Host "Inside deploy.ps1::Bring-Website-Online: Deleting App_Offline.htm from '$ServicePath'..."
    
    $documentPath = (Join-Path $ServicePath $appOfflineDocFileName)
    $documentExists = test-path -path $documentPath

    ## Most likely, the App_Offline.htm will not exist if the DoNotDeleteRule is enabled.
    if($documentExists -eq $true)
    {
        remove-item (Join-Path $ServicePath $appOfflineDocFileName) -force
    }
    else
    {
        Write-Host "Inside deploy.ps1::Bring-Website-Online: Skipping deletion of App_Offline.htm since it does not exist in folder: '$ServicePath'."
    }
    
    Write-Host "Inside deploy.ps1::Bring-Website-Online: Operation completed successfully."
}

function Get-IisAppPools 
{
    param()

    ## Note: Get-WmiObject against namespace 'root\MicrosoftIISv2' will only work on Windows Vista/7/2008/2008R2/8/2012 if 'IIS 6.0 Compatibility' is installed.
    ##       Since we don't want to make that assumption or force this installation as a pre-req, we need to execute different commands based on the version of
    ##       IIS available to us.
    if ([System.Environment]::OSVersion.Version.Major -lt 6)
    {
        ## Windows 2003 and below (IIS 5.x, 6.0)
        Get-WmiObject -ComputerName "localhost" -Namespace "root\MicrosoftIISv2" -Class "IIsApplicationPool" -Filter 'name like "W3SVC/APPPOOLS/%"' | ForEach-Object { $_.Name.ToString().SubString(15) } 
    }
    else
    {
        ## Windows Vista/2008 and above (IIS 7.x and above)
        [string]$command = ('{0}\system32\inetsrv\appcmd.exe list apppools' -f $env:SystemRoot)
        cmd /C $command
    }
}

function Start-IisAppPool
{
    param([string]$appPoolName)

    if ([System.Environment]::OSVersion.Version.Major -lt 6)
    {
        (Get-WmiObject -ComputerName "localhost" -Query "SELECT * FROM IIsApplicationPool WHERE Name = 'W3SVC/AppPools/$appPoolName'" -Namespace 'root\MicrosoftIISv2').Start()
    }
    else
    {
        [string]$command = ('{0}\system32\inetsrv\appcmd.exe start apppool /apppool.name:"{1}" /wait:true /timeout:{2}' -f $env:SystemRoot, $appPoolName, $appPoolWaitTimeoutMilliseconds)
        cmd /C $command
    }
}

function Stop-IisAppPool
{
    param([string]$appPoolName)

    if ([System.Environment]::OSVersion.Version.Major -lt 6)
    {
        (Get-WmiObject -ComputerName "localhost" -Query "SELECT * FROM IIsApplicationPool WHERE Name = 'W3SVC/AppPools/$appPoolName'" -Namespace 'root\MicrosoftIISv2').Stop()
    }
    else
    {
        [string]$command = ('{0}\system32\inetsrv\appcmd.exe stop apppool /apppool.name:"{1}" /wait:true /timeout:{2}' -f $env:SystemRoot, $appPoolName, $appPoolWaitTimeoutMilliseconds)
        cmd /C $command
    }
}

function Recycle-IisAppPool
{
    param([string]$appPoolName)

    if ([System.Environment]::OSVersion.Version.Major -lt 6)
    {
        (Get-WmiObject -ComputerName "localhost" -Query "SELECT * FROM IIsApplicationPool WHERE Name = 'W3SVC/AppPools/$appPoolName'" -Namespace 'root\MicrosoftIISv2').Recycle()
    }
    else
    {
        [string]$command = ('{0}\system32\inetsrv\appcmd.exe recycle apppool /apppool.name:"{1}"' -f $env:SystemRoot, $appPoolName)
        cmd /C $command
    }
}

function Clear-Temp-AspNet-Cache
{
    $AspNetVersion = GetArgumentValue("AspNetVersion")
    
    Write-Host ("Inside Clear-Temp-AspNet-Cache [AspNetVersion={0}]..." -f $AspNetVersion)

    if($AspNetVersion -eq $null -Or $AspNetVersion -eq "")
    {
        $AspNetVersion = $defaultAspNetVersion
    }

    $windowsFolder = $env:windir
    $aspNetCacheLocationLocation = $aspNetCacheLocationTemplate -f $windowsFolder, $AspNetVersion
    $aspNetCacheLocationLocation64 = $aspNetCacheLocationTemplate64 -f $windowsFolder, $AspNetVersion

    Write-Host ("aspNetCacheLocationLocation: '{0}'..." -f $aspNetCacheLocationLocation)
    Write-Host ("aspNetCacheLocationLocation64: '{0}'..." -f $aspNetCacheLocationLocation64)
    
    if([System.IO.Directory]::Exists($aspNetCacheLocationLocation))
    {
        $aspNetCacheLocationLocationWildcard = "{0}\*" -f $aspNetCacheLocationLocation
        Write-Host ("Deleting '{0}'..." -f $aspNetCacheLocationLocationWildcard)
        Remove-Item $aspNetCacheLocationLocationWildcard -force -recurse
    }

    if([System.IO.Directory]::Exists($aspNetCacheLocationLocation64))
    {
        $aspNetCacheLocationLocation64Wildcard = "{0}\*" -f $aspNetCacheLocationLocation64
        Write-Host ("Deleting '{0}'..." -f $aspNetCacheLocationLocation64Wildcard)
        Remove-Item $aspNetCacheLocationLocation64Wildcard -force -recurse
    }
}

####################################################
##                                                ##
##          Active Directory Utilities.           ##
##                                                ##
####################################################
Add-Man "Get-Servers-By-Pattern" "Gets all servers in based on a given regex pattern." "ActiveDirectory"
function Get-Servers-By-Pattern
{
    param([string]$pattern)

    try
    {
        ## Chunk of code extracted from Paul's email.
        $root = New-Object System.DirectoryServices.DirectoryEntry
        $rootSearch = New-Object System.DirectoryServices.DirectorySearcher
        $rootSearch.SearchRoot = $root
        $rootSearch.Filter = ("(objectCategory=computer)")
        $propList = "name"

        ForEach ($prop in $propList) {
            $temp = $rootSearch.PropertiesToLoad.Add($prop)
        }
        
        $results=$rootSearch.FindAll()
        $servers = @()
        
        foreach($result in $results) 
        {
            $servers += $result.Properties.name
        }
        $filteredServerNames = $servers -match $pattern
        
        return ($filteredServerNames | sort)
    }
    catch
    {
        Write-Host "ERROR:[Get-Servers-By-Pattern]: Encountered errors: $_" -foregroundcolor red
        exit 1
    }
}

Add-Man "Get-Servers-In-Site" "Gets all servers in a given site." "ActiveDirectory"
function Get-Servers-In-Site
{
    param([string]$siteName)

    ##  Taken from:
    ##  http://blogs.msdn.com/b/adpowershell/archive/2009/08/18/active-directory-powershell-to-manage-sites-and-subnets-part-3-getting-site-and-subnets.aspx
    ##  Get all Servers in a specified Active Directory site.
    ##
    ##  An example call to print the list of servers in a given AD site:
    ##     $servers = Get-Servers-In-Site("Default-First-Site-Name")
    ##     foreach($server in $servers)
    ##     {
    ##       Write-Host "server: " ($server.DNSHostName)
    ##     }
    ##

    try
    {
        Import-Module ActiveDirectory -CmdLet Get-ADRootDSE,Get-ADObject -errorAction SilentlyContinue

        ##  Get all Servers in a specified Active Directory site.

        #$siteName =   "Default-First-Site-Name"
        $configNCDN = (Get-ADRootDSE).ConfigurationNamingContext
        $siteContainerDN = ("CN=Sites," + $configNCDN)
        $serverContainerDN = "CN=Servers,CN=" + $siteName + "," + $siteContainerDN
        Get-ADObject -SearchBase $serverContainerDN -SearchScope OneLevel -filter { objectClass -eq "Server" } -Properties "DNSHostName" | Select Name, DNSHostName
    }
    catch
    {
        Write-Host "ERROR:[Get-Servers-In-Site]: Encountered errors: $_" -foregroundcolor red
        exit 1
    }
    finally
    {
        Remove-Module ActiveDirectory -errorAction SilentlyContinue
    }
}

####################################################
##                                                ##
##             Miscellaneous functions            ##
##                                                ##
####################################################
Add-Man "Do-PostSync" "Used for running any post deployment actions (like starting a service or app pool)." "Deployment"
function Do-PostSync()
{
    Write-Host "Inside deploy.ps1::Do-PostSync..."
}

Add-Man "Do-Finish" "Used for cleanup after a new deployment." "Deployment"
function Do-Finish()
{
    Write-Host "Inside deploy.ps1::Do-Finish."
    
    # Perform cleanup here. Delete temp tools folder contents (leave any log files for troubleshooting).
    if($cleanupTempToolsFolderOnExit -eq $true)
    {
        Remove-Item ($remoteTempToolsPath + "\*") -recurse -force -exclude *log
    }
}

function RunPreDeployCommand()
{
    Write-Host "Inside deploy.ps1::RunPreDeployCommand..."

    $PreDeployCommand = GetArgumentValue("PreDeployCommand")
    
    if($PreDeployCommand -eq $null -Or $PreDeployCommand -eq "")
    {
        Write-Host "RunPreDeployCommand: PreDeployCommand was empty..."
    }
    else
    {
        Write-Host "RunPreDeployCommand: Running PreDeployCommand: $PreDeployCommand"
        try
        {
            CMD.EXE /C "$PreDeployCommand"
        }
        catch
        {
            Write-Host "ERROR:[RunPreDeployCommand]: Encountered errors: $_"
        }
    }

    Write-Host "RunPreDeployCommand: Completed."
}

function RunPostDeployCommand()
{
    Write-Host "Inside deploy.ps1::RunPostDeployCommand..."

    $PostDeployCommand = GetArgumentValue("PostDeployCommand")
    
    if($PostDeployCommand -eq $null -Or $PostDeployCommand -eq "")
    {
        Write-Host "RunPostDeployCommand: PostDeployCommand was empty..."
    }
    else
    {
        Write-Host "RunPostDeployCommand: Running PostDeployCommand: $PostDeployCommand"
        try
        {
            CMD.EXE /C "$PostDeployCommand"
        }
        catch
        {
            Write-Host "ERROR:[RunPostDeployCommand]: Encountered errors: $_"
        }
    }

    Write-Host "RunPostDeployCommand: Completed."
}

function GetArgumentValue()
{
    param([string]$argumentName)
    
    if($argumentName -eq "OldServiceName" -Or $argumentName -eq "ServiceName" -Or $argumentName -eq "ServicePath" -Or $argumentName -eq "IisApplicationPoolName" -Or $argumentName -eq "PreDeployCommand" -Or $argumentName -eq "PostDeployCommand")
    {
        [string]$argumentValue = $hashTable[$argumentName]
        return $argumentValue.Replace($spaceReplacementValue, " ")
    }
    else
    {
        return $hashTable[$argumentName]
    }
}

Add-Man "Write-To-Log" "Write the output to the specified log file after sanitizing it. Also filters out any errors found in the output." "Deployment"
function Write-To-Log()
{
    param([string]$logFilePath, [system.collections.arraylist]$output, [system.collections.arraylist]$errorsList, [string]$passwordToRemove)

    if($passwordToRemove -ne $null -And $passwordToRemove -ne "")
    {
        #Write-Host "Removing passwords from logs..."
        for($i=0;$i -lt $output.Count; $i++)
        {
            $output[$i] = $output[$i] | foreach {$_.Replace($passwordToRemove, $passwordHideString)}
        }
    }
    
    $output | Set-Content $logFilePath
    
    ## Extract any errors from the log.
    Extract-Errors $output $errorsList
}

Add-Man "Extract-Errors" "Extracts error messages from the provided content." "Deployment"
function Extract-Errors()
{
    param([system.collections.arraylist]$content, [system.collections.arraylist]$errorsList)
    
    ## Pattern used in parsing the error lines from the output.
    $pattern = "^ERROR:\[[^]]+?\]:\s.*$"
    $regex = new-object Text.RegularExpressions.Regex $pattern, ('multiline')
    
    foreach($contentItem in $content)
    {
        $matches = $regex.Matches($contentItem)
        if ($matches.count -gt 0)
        {
            $message = "Found {0} matches." -f $matches.count
            #Write-Host $message

            foreach($match in $matches)
            {
                $addIndex = $errorsList.Add($match.Value)
            }
        }
        else
        {
            #Write-Host "No matches found. No errors extracted."
        }
    }
}

Add-Man "Is-Host-Accessible" "Checks if the specified host is accessible by pinging it." "Deployment"
function Is-Host-Accessible()
{
    param([string]$hostname)
    
    ## Ping the host to see if it is alive.
    if (test-connection -computername "$hostname" -quiet)
    {
        Write-Host "Ping succeeded." -foregroundcolor green
        return $true
    }
    else
    {
        Write-Host "Ping failed." -foregroundcolor red
        return $false
    }
}

function Ensure-Process-Unloaded()
{
    $ExeName = GetArgumentValue("ExeName")
    
    if($ExeName -ne $null -And $ExeName -ne "")
    {
        Kill-Process-If-Still-Active $ExeName
    }
}

function Kill-Process-If-Still-Active()
{
    Param([string]$processName)
    
    if($processName.EndsWith(".exe"))
    {
        $processName = $processName.Substring(0, $processName.Length - 4)
    }
    
    Write-Host "ProcessName: $processName"

    ## Iterate for as many times as specified in "defaultNumberOfProcessWaitBeforeKillIterations" and wait/sleep for "defaultProcessWaitBeforeKillInMilliseconds"
    ## in between iterations. This is due to the fact that some services take some time to shutdown, and it is usually not a good idea to kill the service while it's
    ## doing that due to the risk of corrupting data being saved to disk.
    $processIsGone = $false
    $iterationCounter = $defaultNumberOfProcessWaitBeforeKillIterations - 1
    if($iterationCounter -lt 0)
    {
        $iterationCounter = 0
    }
    
    do
    {
        Write-Host "Iteration: $iterationCounter"
        
        $process = get-process $processName -errorAction SilentlyContinue
        if($process)
        {
            Write-Host "Process was found."
            
            ## Wait before attempting to find and kill the process.
            Write-Host "Waiting for $defaultProcessWaitBeforeKillInMilliseconds milliseconds before attempting to kill process."
            Start-Sleep -m $defaultProcessWaitBeforeKillInMilliseconds

            stop-process -force -inputobject $process -errorAction SilentlyContinue
            Write-Host "Process was killed."
        }
        else
        {
            Write-Host "Process was not found."
            $processIsGone = $true
        }
    } until ($iterationCounter-- -le 0 -Or $processIsGone -eq $true)
}


###########################################################################################################################
###########################################################################################################################
############################################                                      #########################################
############################################             STARTUP ROUTINE          #########################################
############################################                                      #########################################
###########################################################################################################################
###########################################################################################################################




#####################################################
##                                                 ##
##  Runs all command line arguments as functions.  ##
##                                                 ##
#####################################################
Write-Host "Deployment Script Version: $deploymentScriptVersion."

Write-Host "Checking PowerShell version installed (must be 2.0 or above)..."
[int]$powerShellVersionNumber = $PSVersionTable.PSVersion.Major
Write-Host "PowerShell Version: $powerShellVersionNumber."

if($powerShellVersionNumber -lt 2)
{
    Write-Host "PowerShell V2.0 or above is required for running this script. Installed version is $powerShellVersionNumber." -ForegroundColor Red
    exit 1;
}

Write-Host "Running all commands..."

## this hash table will hold any arguments passed to the script in the form name=value.
$hashTable = @{}

## First pass: populate the hashtable:
$args | ForEach-Object { if (Get-Command $_ -errorAction SilentlyContinue){ <# Do Nothing #> } else { $nv = $_ -split '=' ; $hashTable[$nv[0]] += $nv[1] } } 

## Second pass: execute the commands. Needs to happen in the second phase to guarantee that the arguments were processed since they're needed in the functions.
$args | ForEach-Object { if (Get-Command $_ -errorAction SilentlyContinue){ & $_ } }

## Uncomment to print the hash table contents.
#$hashTable

Write-Host "Done running commands. Exiting..."

## Kill the process to close script.
#Get-Process -Name "powershell" | Stop-Process

## Uncomment to kill the host process.
#Get-Process -id $pid | Stop-Process
